Updated string prefix and suffix checker slicing to startswith() and endswith() methods. Using startswith() and endswith() improves readability, error-proneness and enhances maintainability. Change-Id: I1d5fbf116a61763346c6f92fd8023dbbe9bb37cf
		
			
				
	
	
		
			301 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			301 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Copyright (c) 2010-2012 OpenStack Foundation
 | 
						|
#
 | 
						|
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
# you may not use this file except in compliance with the License.
 | 
						|
# You may obtain a copy of the License at
 | 
						|
#
 | 
						|
#    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
						|
# implied.
 | 
						|
# See the License for the specific language governing permissions and
 | 
						|
# limitations under the License.
 | 
						|
 | 
						|
import json
 | 
						|
 | 
						|
from swift.common.utils import urlparse
 | 
						|
 | 
						|
 | 
						|
def clean_acl(name, value):
 | 
						|
    """
 | 
						|
    Returns a cleaned ACL header value, validating that it meets the formatting
 | 
						|
    requirements for standard Swift ACL strings.
 | 
						|
 | 
						|
    The ACL format is::
 | 
						|
 | 
						|
        [item[,item...]]
 | 
						|
 | 
						|
    Each item can be a group name to give access to or a referrer designation
 | 
						|
    to grant or deny based on the HTTP Referer header.
 | 
						|
 | 
						|
    The referrer designation format is::
 | 
						|
 | 
						|
        .r:[-]value
 | 
						|
 | 
						|
    The ``.r`` can also be ``.ref``, ``.referer``, or ``.referrer``; though it
 | 
						|
    will be shortened to just ``.r`` for decreased character count usage.
 | 
						|
 | 
						|
    The value can be ``*`` to specify any referrer host is allowed access, a
 | 
						|
    specific host name like ``www.example.com``, or if it has a leading period
 | 
						|
    ``.`` or leading ``*.`` it is a domain name specification, like
 | 
						|
    ``.example.com`` or ``*.example.com``. The leading minus sign ``-``
 | 
						|
    indicates referrer hosts that should be denied access.
 | 
						|
 | 
						|
    Referrer access is applied in the order they are specified. For example,
 | 
						|
    .r:.example.com,.r:-thief.example.com would allow all hosts ending with
 | 
						|
    .example.com except for the specific host thief.example.com.
 | 
						|
 | 
						|
    Example valid ACLs::
 | 
						|
 | 
						|
        .r:*
 | 
						|
        .r:*,.r:-.thief.com
 | 
						|
        .r:*,.r:.example.com,.r:-thief.example.com
 | 
						|
        .r:*,.r:-.thief.com,bobs_account,sues_account:sue
 | 
						|
        bobs_account,sues_account:sue
 | 
						|
 | 
						|
    Example invalid ACLs::
 | 
						|
 | 
						|
        .r:
 | 
						|
        .r:-
 | 
						|
 | 
						|
    By default, allowing read access via .r will not allow listing objects in
 | 
						|
    the container -- just retrieving objects from the container. To turn on
 | 
						|
    listings, use the .rlistings directive.
 | 
						|
 | 
						|
    Also, .r designations aren't allowed in headers whose names include the
 | 
						|
    word 'write'.
 | 
						|
 | 
						|
    ACLs that are "messy" will be cleaned up. Examples:
 | 
						|
 | 
						|
    ======================  ======================
 | 
						|
    Original                Cleaned
 | 
						|
    ----------------------  ----------------------
 | 
						|
    ``bob, sue``            ``bob,sue``
 | 
						|
    ``bob , sue``           ``bob,sue``
 | 
						|
    ``bob,,,sue``           ``bob,sue``
 | 
						|
    ``.referrer : *``       ``.r:*``
 | 
						|
    ``.ref:*.example.com``  ``.r:.example.com``
 | 
						|
    ``.r:*, .rlistings``    ``.r:*,.rlistings``
 | 
						|
    ======================  ======================
 | 
						|
 | 
						|
    :param name: The name of the header being cleaned, such as X-Container-Read
 | 
						|
                 or X-Container-Write.
 | 
						|
    :param value: The value of the header being cleaned.
 | 
						|
    :returns: The value, cleaned of extraneous formatting.
 | 
						|
    :raises ValueError: If the value does not meet the ACL formatting
 | 
						|
                        requirements; the error message will indicate why.
 | 
						|
    """
 | 
						|
    name = name.lower()
 | 
						|
    values = []
 | 
						|
    for raw_value in value.split(','):
 | 
						|
        raw_value = raw_value.strip()
 | 
						|
        if not raw_value:
 | 
						|
            continue
 | 
						|
        if ':' not in raw_value:
 | 
						|
            values.append(raw_value)
 | 
						|
            continue
 | 
						|
        first, second = (v.strip() for v in raw_value.split(':', 1))
 | 
						|
        if not first or not first.startswith('.'):
 | 
						|
            values.append(raw_value)
 | 
						|
        elif first in ('.r', '.ref', '.referer', '.referrer'):
 | 
						|
            if 'write' in name:
 | 
						|
                raise ValueError('Referrers not allowed in write ACL: '
 | 
						|
                                 '%s' % repr(raw_value))
 | 
						|
            negate = False
 | 
						|
            if second and second.startswith('-'):
 | 
						|
                negate = True
 | 
						|
                second = second[1:].strip()
 | 
						|
            if second and second != '*' and second.startswith('*'):
 | 
						|
                second = second[1:].strip()
 | 
						|
            if not second or second == '.':
 | 
						|
                raise ValueError('No host/domain value after referrer '
 | 
						|
                                 'designation in ACL: %s' % repr(raw_value))
 | 
						|
            values.append('.r:%s%s' % ('-' if negate else '', second))
 | 
						|
        else:
 | 
						|
            raise ValueError('Unknown designator %s in ACL: %s' %
 | 
						|
                             (repr(first), repr(raw_value)))
 | 
						|
    return ','.join(values)
 | 
						|
 | 
						|
 | 
						|
def format_acl_v1(groups=None, referrers=None, header_name=None):
 | 
						|
    """
 | 
						|
    Returns a standard Swift ACL string for the given inputs.
 | 
						|
 | 
						|
    Caller is responsible for ensuring that :referrers: parameter is only given
 | 
						|
    if the ACL is being generated for X-Container-Read.  (X-Container-Write
 | 
						|
    and the account ACL headers don't support referrers.)
 | 
						|
 | 
						|
    :param groups: a list of groups (and/or members in most auth systems) to
 | 
						|
                   grant access
 | 
						|
    :param referrers: a list of referrer designations (without the leading .r:)
 | 
						|
    :param header_name: (optional) header name of the ACL we're preparing, for
 | 
						|
                        clean_acl; if None, returned ACL won't be cleaned
 | 
						|
    :returns: a Swift ACL string for use in X-Container-{Read,Write},
 | 
						|
              X-Account-Access-Control, etc.
 | 
						|
    """
 | 
						|
    groups, referrers = groups or [], referrers or []
 | 
						|
    referrers = ['.r:%s' % r for r in referrers]
 | 
						|
    result = ','.join(groups + referrers)
 | 
						|
    return (clean_acl(header_name, result) if header_name else result)
 | 
						|
 | 
						|
 | 
						|
def format_acl_v2(acl_dict):
 | 
						|
    """
 | 
						|
    Returns a version-2 Swift ACL JSON string.
 | 
						|
 | 
						|
    HTTP headers for Version 2 ACLs have the following form:
 | 
						|
      Header-Name: {"arbitrary":"json","encoded":"string"}
 | 
						|
 | 
						|
    JSON will be forced ASCII (containing six-char \uNNNN sequences rather
 | 
						|
    than UTF-8; UTF-8 is valid JSON but clients vary in their support for
 | 
						|
    UTF-8 headers), and without extraneous whitespace.
 | 
						|
 | 
						|
    Advantages over V1: forward compatibility (new keys don't cause parsing
 | 
						|
    exceptions); Unicode support; no reserved words (you can have a user
 | 
						|
    named .rlistings if you want).
 | 
						|
 | 
						|
    :param acl_dict: dict of arbitrary data to put in the ACL; see specific
 | 
						|
                     auth systems such as tempauth for supported values
 | 
						|
    :returns: a JSON string which encodes the ACL
 | 
						|
    """
 | 
						|
    return json.dumps(acl_dict, ensure_ascii=True, separators=(',', ':'),
 | 
						|
                      sort_keys=True)
 | 
						|
 | 
						|
 | 
						|
def format_acl(version=1, **kwargs):
 | 
						|
    """
 | 
						|
    Compatibility wrapper to help migrate ACL syntax from version 1 to 2.
 | 
						|
    Delegates to the appropriate version-specific format_acl method, defaulting
 | 
						|
    to version 1 for backward compatibility.
 | 
						|
 | 
						|
    :param kwargs: keyword args appropriate for the selected ACL syntax version
 | 
						|
                   (see :func:`format_acl_v1` or :func:`format_acl_v2`)
 | 
						|
    """
 | 
						|
    if version == 1:
 | 
						|
        return format_acl_v1(
 | 
						|
            groups=kwargs.get('groups'), referrers=kwargs.get('referrers'),
 | 
						|
            header_name=kwargs.get('header_name'))
 | 
						|
    elif version == 2:
 | 
						|
        return format_acl_v2(kwargs.get('acl_dict'))
 | 
						|
    raise ValueError("Invalid ACL version: %r" % version)
 | 
						|
 | 
						|
 | 
						|
def parse_acl_v1(acl_string):
 | 
						|
    """
 | 
						|
    Parses a standard Swift ACL string into a referrers list and groups list.
 | 
						|
 | 
						|
    See :func:`clean_acl` for documentation of the standard Swift ACL format.
 | 
						|
 | 
						|
    :param acl_string: The standard Swift ACL string to parse.
 | 
						|
    :returns: A tuple of (referrers, groups) where referrers is a list of
 | 
						|
              referrer designations (without the leading .r:) and groups is a
 | 
						|
              list of groups to allow access.
 | 
						|
    """
 | 
						|
    referrers = []
 | 
						|
    groups = []
 | 
						|
    if acl_string:
 | 
						|
        for value in acl_string.split(','):
 | 
						|
            if value.startswith('.r:'):
 | 
						|
                referrers.append(value[len('.r:'):])
 | 
						|
            else:
 | 
						|
                groups.append(value)
 | 
						|
    return referrers, groups
 | 
						|
 | 
						|
 | 
						|
def parse_acl_v2(data):
 | 
						|
    """
 | 
						|
    Parses a version-2 Swift ACL string and returns a dict of ACL info.
 | 
						|
 | 
						|
    :param data: string containing the ACL data in JSON format
 | 
						|
    :returns: A dict (possibly empty) containing ACL info, e.g.:
 | 
						|
              {"groups": [...], "referrers": [...]}
 | 
						|
    :returns: None if data is None, is not valid JSON or does not parse
 | 
						|
        as a dict
 | 
						|
    :returns: empty dictionary if data is an empty string
 | 
						|
    """
 | 
						|
    if data is None:
 | 
						|
        return None
 | 
						|
    if data is '':
 | 
						|
        return {}
 | 
						|
    try:
 | 
						|
        result = json.loads(data)
 | 
						|
        return (result if type(result) is dict else None)
 | 
						|
    except ValueError:
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
def parse_acl(*args, **kwargs):
 | 
						|
    """
 | 
						|
    Compatibility wrapper to help migrate ACL syntax from version 1 to 2.
 | 
						|
    Delegates to the appropriate version-specific parse_acl method, attempting
 | 
						|
    to determine the version from the types of args/kwargs.
 | 
						|
 | 
						|
    :param args: positional args for the selected ACL syntax version
 | 
						|
    :param kwargs: keyword args for the selected ACL syntax version
 | 
						|
                   (see :func:`parse_acl_v1` or :func:`parse_acl_v2`)
 | 
						|
    :returns: the return value of :func:`parse_acl_v1` or :func:`parse_acl_v2`
 | 
						|
    """
 | 
						|
    version = kwargs.pop('version', None)
 | 
						|
    if version in (1, None):
 | 
						|
        return parse_acl_v1(*args)
 | 
						|
    elif version == 2:
 | 
						|
        return parse_acl_v2(*args, **kwargs)
 | 
						|
    else:
 | 
						|
        raise ValueError('Unknown ACL version: parse_acl(%r, %r)' %
 | 
						|
                         (args, kwargs))
 | 
						|
 | 
						|
 | 
						|
def referrer_allowed(referrer, referrer_acl):
 | 
						|
    """
 | 
						|
    Returns True if the referrer should be allowed based on the referrer_acl
 | 
						|
    list (as returned by :func:`parse_acl`).
 | 
						|
 | 
						|
    See :func:`clean_acl` for documentation of the standard Swift ACL format.
 | 
						|
 | 
						|
    :param referrer: The value of the HTTP Referer header.
 | 
						|
    :param referrer_acl: The list of referrer designations as returned by
 | 
						|
                         :func:`parse_acl`.
 | 
						|
    :returns: True if the referrer should be allowed; False if not.
 | 
						|
    """
 | 
						|
    allow = False
 | 
						|
    if referrer_acl:
 | 
						|
        rhost = urlparse(referrer or '').hostname or 'unknown'
 | 
						|
        for mhost in referrer_acl:
 | 
						|
            if mhost.startswith('-'):
 | 
						|
                mhost = mhost[1:]
 | 
						|
                if mhost == rhost or (mhost.startswith('.') and
 | 
						|
                                      rhost.endswith(mhost)):
 | 
						|
                    allow = False
 | 
						|
            elif mhost == '*' or mhost == rhost or \
 | 
						|
                    (mhost.startswith('.') and rhost.endswith(mhost)):
 | 
						|
                allow = True
 | 
						|
    return allow
 | 
						|
 | 
						|
 | 
						|
def acls_from_account_info(info):
 | 
						|
    """
 | 
						|
    Extract the account ACLs from the given account_info, and return the ACLs.
 | 
						|
 | 
						|
    :param info: a dict of the form returned by get_account_info
 | 
						|
    :returns: None (no ACL system metadata is set), or a dict of the form::
 | 
						|
       {'admin': [...], 'read-write': [...], 'read-only': [...]}
 | 
						|
 | 
						|
    :raises ValueError: on a syntactically invalid header
 | 
						|
    """
 | 
						|
    acl = parse_acl(
 | 
						|
        version=2, data=info.get('sysmeta', {}).get('core-access-control'))
 | 
						|
    if acl is None:
 | 
						|
        return None
 | 
						|
    admin_members = acl.get('admin', [])
 | 
						|
    readwrite_members = acl.get('read-write', [])
 | 
						|
    readonly_members = acl.get('read-only', [])
 | 
						|
    if not any((admin_members, readwrite_members, readonly_members)):
 | 
						|
        return None
 | 
						|
    return {
 | 
						|
        'admin': admin_members,
 | 
						|
        'read-write': readwrite_members,
 | 
						|
        'read-only': readonly_members,
 | 
						|
    }
 |