diff options
author | Ruan HE <ruan.he@orange.com> | 2016-06-09 08:12:34 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@172.30.200.206> | 2016-06-09 08:12:34 +0000 |
commit | 4bc079a2664f9a407e332291f34d174625a9d5ea (patch) | |
tree | 7481cd5d0a9b3ce37c44c797a1e0d39881221cbe /keystone-moon/keystone/common/controller.py | |
parent | 2f179c5790fbbf6144205d3c6e5089e6eb5f048a (diff) | |
parent | 2e7b4f2027a1147ca28301e4f88adf8274b39a1f (diff) |
Merge "Update Keystone core to Mitaka."
Diffstat (limited to 'keystone-moon/keystone/common/controller.py')
-rw-r--r-- | keystone-moon/keystone/common/controller.py | 196 |
1 files changed, 99 insertions, 97 deletions
diff --git a/keystone-moon/keystone/common/controller.py b/keystone-moon/keystone/common/controller.py index 56bc211a..8672525f 100644 --- a/keystone-moon/keystone/common/controller.py +++ b/keystone-moon/keystone/common/controller.py @@ -36,21 +36,39 @@ CONF = cfg.CONF def v2_deprecated(f): - """No-op decorator in preparation for deprecating Identity API v2. - - This is a placeholder for the pending deprecation of v2. The implementation - of this decorator can be replaced with:: - - from oslo_log import versionutils - - - v2_deprecated = versionutils.deprecated( - what='v2 API', - as_of=versionutils.deprecated.JUNO, - in_favor_of='v3 API') - - """ - return f + @six.wraps(f) + def wrapper(*args, **kwargs): + deprecated = versionutils.deprecated( + what=f.__name__ + ' of the v2 API', + as_of=versionutils.deprecated.MITAKA, + in_favor_of='a similar function in the v3 API', + remove_in=+4) + return deprecated(f) + return wrapper() + + +def v2_ec2_deprecated(f): + @six.wraps(f) + def wrapper(*args, **kwargs): + deprecated = versionutils.deprecated( + what=f.__name__ + ' of the v2 EC2 APIs', + as_of=versionutils.deprecated.MITAKA, + in_favor_of=('a similar function in the v3 Credential APIs'), + remove_in=0) + return deprecated(f) + return wrapper() + + +def v2_auth_deprecated(f): + @six.wraps(f) + def wrapper(*args, **kwargs): + deprecated = versionutils.deprecated( + what=f.__name__ + ' of the v2 Authentication APIs', + as_of=versionutils.deprecated.MITAKA, + in_favor_of=('a similar function in the v3 Authentication APIs'), + remove_in=0) + return deprecated(f) + return wrapper() def _build_policy_check_credentials(self, action, context, kwargs): @@ -165,24 +183,32 @@ def protected(callback=None): return wrapper -def filterprotected(*filters): - """Wraps filtered API calls with role based access controls (RBAC).""" +def filterprotected(*filters, **callback): + """Wraps API list calls with role based access controls (RBAC). + This handles both the protection of the API parameters as well as any + filters supplied. + + More complex API list calls (for example that need to examine the contents + of an entity referenced by one of the filters) should pass in a callback + function, that will be subsequently called to check protection for these + multiple entities. This callback function should gather the appropriate + entities needed and then call check_protection() in the V3Controller class. + + """ def _filterprotected(f): @functools.wraps(f) def wrapper(self, context, **kwargs): if not context['is_admin']: - action = 'identity:%s' % f.__name__ - creds = _build_policy_check_credentials(self, action, - context, kwargs) - # Now, build the target dict for policy check. We include: + # The target dict for the policy check will include: # # - Any query filter parameters # - Data from the main url (which will be in the kwargs - # parameter) and would typically include the prime key - # of a get/update/delete call + # parameter), which although most of our APIs do not utilize, + # in theory you could have. # - # First any query filter parameters + + # First build the dict of filter parameters target = dict() if filters: for item in filters: @@ -193,15 +219,29 @@ def filterprotected(*filters): ', '.join(['%s=%s' % (item, target[item]) for item in target]))) - # Now any formal url parameters - for key in kwargs: - target[key] = kwargs[key] - - self.policy_api.enforce(creds, - action, - utils.flatten_dict(target)) - - LOG.debug('RBAC: Authorization granted') + if 'callback' in callback and callback['callback'] is not None: + # A callback has been specified to load additional target + # data, so pass it the formal url params as well as the + # list of filters, so it can augment these and then call + # the check_protection() method. + prep_info = {'f_name': f.__name__, + 'input_attr': kwargs, + 'filter_attr': target} + callback['callback'](self, context, prep_info, **kwargs) + else: + # No callback, so we are going to check the protection here + action = 'identity:%s' % f.__name__ + creds = _build_policy_check_credentials(self, action, + context, kwargs) + # Add in any formal url parameters + for key in kwargs: + target[key] = kwargs[key] + + self.policy_api.enforce(creds, + action, + utils.flatten_dict(target)) + + LOG.debug('RBAC: Authorization granted') else: LOG.warning(_LW('RBAC: Bypassing authorization')) return f(self, context, filters, **kwargs) @@ -211,6 +251,7 @@ def filterprotected(*filters): class V2Controller(wsgi.Application): """Base controller class for Identity API v2.""" + def _normalize_domain_id(self, context, ref): """Fill in domain_id since v2 calls are not domain-aware. @@ -224,27 +265,13 @@ class V2Controller(wsgi.Application): @staticmethod def filter_domain_id(ref): """Remove domain_id since v2 calls are not domain-aware.""" - if 'domain_id' in ref: - if ref['domain_id'] != CONF.identity.default_domain_id: - raise exception.Unauthorized( - _('Non-default domain is not supported')) - del ref['domain_id'] + ref.pop('domain_id', None) return ref @staticmethod def filter_domain(ref): - """Remove domain since v2 calls are not domain-aware. - - V3 Fernet tokens builds the users with a domain in the token data. - This method will ensure that users create in v3 belong to the default - domain. - - """ - if 'domain' in ref: - if ref['domain'].get('id') != CONF.identity.default_domain_id: - raise exception.Unauthorized( - _('Non-default domain is not supported')) - del ref['domain'] + """Remove domain since v2 calls are not domain-aware.""" + ref.pop('domain', None) return ref @staticmethod @@ -287,20 +314,13 @@ class V2Controller(wsgi.Application): def v3_to_v2_user(ref): """Convert a user_ref from v3 to v2 compatible. - - v2.0 users are not domain aware, and should have domain_id validated - to be the default domain, and then removed. - - - v2.0 users expect the use of tenantId instead of default_project_id. - - - v2.0 users have a username attribute. - - This method should only be applied to user_refs being returned from the - v2.0 controller(s). + * v2.0 users are not domain aware, and should have domain_id removed + * v2.0 users expect the use of tenantId instead of default_project_id + * v2.0 users have a username attribute If ref is a list type, we will iterate through each element and do the conversion. """ - def _format_default_project_id(ref): """Convert default_project_id to tenantId for v2 calls.""" default_project_id = ref.pop('default_project_id', None) @@ -342,7 +362,6 @@ class V2Controller(wsgi.Application): If ref is a list type, we will iterate through each element and do the conversion. """ - def _filter_project_properties(ref): """Run through the various filter methods.""" V2Controller.filter_domain_id(ref) @@ -404,8 +423,6 @@ class V3Controller(wsgi.Application): Class parameters: - * `_mutable_parameters` - set of parameters that can be changed by users. - Usually used by cls.check_immutable_params() * `_public_parameters` - set of parameters that are exposed to the user. Usually used by cls.filter_params() @@ -450,7 +467,6 @@ class V3Controller(wsgi.Application): True, including the absence of a value """ - if (isinstance(filter_value, six.string_types) and filter_value == '0'): val = False @@ -545,7 +561,6 @@ class V3Controller(wsgi.Application): @classmethod def filter_by_attributes(cls, refs, hints): """Filters a list of references by filter values.""" - def _attr_match(ref_attr, val_attr): """Matches attributes allowing for booleans as strings. @@ -565,7 +580,7 @@ class V3Controller(wsgi.Application): :param filter: the filter in question :param ref: the dict to check - :returns True if there is a match + :returns: True if there is a match """ comparator = filter['comparator'] @@ -713,6 +728,8 @@ class V3Controller(wsgi.Application): if token_ref.domain_scoped: return token_ref.domain_id + elif token_ref.project_scoped: + return token_ref.project_domain_id else: LOG.warning( _LW('No domain information specified as part of list request')) @@ -726,7 +743,16 @@ class V3Controller(wsgi.Application): being used. """ - token_ref = utils.get_token_ref(context) + try: + token_ref = utils.get_token_ref(context) + except exception.Unauthorized: + if context.get('is_admin'): + raise exception.ValidationError( + _('You have tried to create a resource using the admin ' + 'token. As this token is not within a domain you must ' + 'explicitly include a domain for this resource to ' + 'belong to.')) + raise if token_ref.domain_scoped: return token_ref.domain_id @@ -751,7 +777,7 @@ class V3Controller(wsgi.Application): def _normalize_domain_id(self, context, ref): """Fill in domain_id if not specified in a v3 call.""" - if 'domain_id' not in ref: + if not ref.get('domain_id'): ref['domain_id'] = self._get_domain_id_from_token(context) return ref @@ -768,7 +794,7 @@ class V3Controller(wsgi.Application): additional entities or attributes (passed in target_attr), so that they can be referenced by policy rules. - """ + """ if 'is_admin' in context and context['is_admin']: LOG.warning(_LW('RBAC: Bypassing authorization')) else: @@ -785,43 +811,19 @@ class V3Controller(wsgi.Application): if target_attr: policy_dict = {'target': target_attr} policy_dict.update(prep_info['input_attr']) + if 'filter_attr' in prep_info: + policy_dict.update(prep_info['filter_attr']) self.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted') @classmethod - def check_immutable_params(cls, ref): - """Raise exception when disallowed parameter is in ref. - - Check whether the ref dictionary representing a request has only - mutable parameters included. If not, raise an exception. This method - checks only root-level keys from a ref dictionary. - - :param ref: a dictionary representing deserialized request to be - stored - :raises: :class:`keystone.exception.ImmutableAttributeError` - - """ - ref_keys = set(ref.keys()) - blocked_keys = ref_keys.difference(cls._mutable_parameters) - - if not blocked_keys: - # No immutable parameters changed - return - - exception_args = {'target': cls.__name__, - 'attributes': ', '.join(blocked_keys)} - raise exception.ImmutableAttributeError(**exception_args) - - @classmethod def filter_params(cls, ref): """Remove unspecified parameters from the dictionary. - This function removes unspecified parameters from the dictionary. See - check_immutable_parameters for corresponding function that raises - exceptions. This method checks only root-level keys from a ref - dictionary. + This function removes unspecified parameters from the dictionary. + This method checks only root-level keys from a ref dictionary. :param ref: a dictionary representing deserialized response to be serialized |