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: # # - 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 # # First any query filter parameters target = dict() if filters: for item in filters: if item in context['query_string']: target[item] = context['query_string'][item] LOG.debug('RBAC: Adding query filter params (%s)', ( ', '.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') else: LOG.warning(_LW('RBAC: Bypassing authorization')) return f(self, context, filters, **kwargs)
def _check_sys_role(self, context, prep_info, *args, **kwargs): target = dict() if 'role_id' in kwargs: # (DWang) Get, delete and update system and domain roles role_ref = self.role_api.get_role(kwargs['role_id']) if role_ref['domain_id'] == CONF.role.sys_role_domain_id: op_obj = prep_info['f_name'].split('_') sys_role_perm = ''.join([op_obj[0], '_sys_', op_obj[1]]) action = ('keystone', sys_role_perm) else: action = ('keystone', prep_info['f_name']) target['obj'] = {'role': role_ref} elif 'role' in kwargs and not kwargs['role']['domain_id']: # (DWang) Create system roles op_obj = prep_info['f_name'].split('_') sys_role_perm = ''.join([op_obj[0], '_sys_', op_obj[1]]) action = ('keystone', sys_role_perm) else: # (DWang) Create domain roles action = ('keystone', prep_info['f_name']) creds = controller._build_policy_check_credentials(self, action, context, kwargs) for k in kwargs.iterkeys(): if isinstance(kwargs[k], dict): target['reqBody.'+k] = kwargs[k] else: target['url.'+k] = kwargs[k] target = utils.flatten_dict(target) self.policy_api.enforce(action, target, creds) LOG.debug('RBAC: Authorization granted')
def check_policy(controller, request, action, filter_attr=None, input_attr=None, target_attr=None, *args, **kwargs): # Makes the arguments from check protection explicit. request.assert_authenticated() if request.context.is_admin: LOG.warning('RBAC: Bypassing authorization') return # TODO(henry-nash) need to log the target attributes as well creds = _build_policy_check_credentials( action, request.context_dict, input_attr) # Build the dict the policy engine will check against from both the # parameters passed into the call we are protecting plus the target # attributes provided. policy_dict = {} _handle_member_from_driver(controller, policy_dict, **kwargs) _handle_subject_token_id(controller, request, policy_dict) if target_attr: policy_dict = {'target': target_attr} if input_attr: policy_dict.update(input_attr) if filter_attr: policy_dict.update(filter_attr) for key in kwargs: policy_dict[key] = kwargs[key] controller.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted')
def filter_by_attributes(cls, refs, hints): """Filter a list of references by filter values.""" def _attr_match(ref_attr, val_attr): """Matche attributes allowing for booleans as strings. We test explicitly for a value that defines it as 'False', which also means that the existence of the attribute with no value implies 'True' """ if type(ref_attr) is bool: return ref_attr == utils.attr_as_boolean(val_attr) else: return ref_attr == val_attr def _inexact_attr_match(filter, ref): """Apply an inexact filter to a result dict. :param filter: the filter in question :param ref: the dict to check :returns: True if there is a match """ comparator = filter['comparator'] key = filter['name'] if key in ref: filter_value = filter['value'] target_value = ref[key] if not filter['case_sensitive']: # We only support inexact filters on strings so # it's OK to use lower() filter_value = filter_value.lower() target_value = target_value.lower() if comparator == 'contains': return (filter_value in target_value) elif comparator == 'startswith': return target_value.startswith(filter_value) elif comparator == 'endswith': return target_value.endswith(filter_value) else: # We silently ignore unsupported filters return True return False for filter in hints.filters: if filter['comparator'] == 'equals': attr = filter['name'] value = filter['value'] refs = [r for r in refs if _attr_match( utils.flatten_dict(r).get(attr), value)] else: # It might be an inexact filter refs = [r for r in refs if _inexact_attr_match( filter, r)] return refs
def check_protection(self, request, prep_info, target_attr=None): """Provide call protection for complex target attributes. As well as including the standard parameters from the original API call (which is passed in prep_info), this call will add in any additional entities or attributes (passed in target_attr), so that they can be referenced by policy rules. """ if request.context.is_admin: LOG.warning(_LW('RBAC: Bypassing authorization')) else: action = 'identity:%s' % prep_info['f_name'] # TODO(henry-nash) need to log the target attributes as well creds = _build_policy_check_credentials(self, action, request.context_dict, prep_info['input_attr']) # Build the dict the policy engine will check against from both the # parameters passed into the call we are protecting (which was # stored in the prep_info by protected()), plus the target # attributes provided. policy_dict = {} 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')
def check_protection(self, context, prep_info, target_attr=None): """Provide call protection for complex target attributes. As well as including the standard parameters from the original API call (which is passed in prep_info), this call will add in any 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: action = 'identity:%s' % prep_info['f_name'] # TODO(henry-nash) need to log the target attributes as well creds = _build_policy_check_credentials(self, action, context, prep_info['input_attr']) # Build the dict the policy engine will check against from both the # parameters passed into the call we are protecting (which was # stored in the prep_info by protected()), plus the target # attributes provided. policy_dict = {} 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')
def inner(self, request, *args, **kwargs): request.assert_authenticated() if request.context.is_admin: LOG.warning(_LW('RBAC: Bypassing authorization')) elif callback is not None: prep_info = {'f_name': f.__name__, 'input_attr': kwargs} callback(self, request, prep_info, *args, **kwargs) else: action = 'identity:%s' % f.__name__ creds = _build_policy_check_credentials( self, action, request.context_dict, kwargs) policy_dict = {} # Check to see if we need to include the target entity in our # policy checks. We deduce this by seeing if the class has # specified a get_member() method and that kwargs contains the # appropriate entity id. if (hasattr(self, 'get_member_from_driver') and self.get_member_from_driver is not None): key = '%s_id' % self.member_name if key in kwargs: ref = self.get_member_from_driver(kwargs[key]) policy_dict['target'] = {self.member_name: ref} # TODO(henry-nash): Move this entire code to a member # method inside v3 Auth if request.context_dict.get('subject_token_id') is not None: window_seconds = self._token_validation_window(request) token_ref = token_model.KeystoneToken( token_id=request.context_dict['subject_token_id'], token_data=self.token_provider_api.validate_token( request.context_dict['subject_token_id'], window_seconds=window_seconds)) policy_dict.setdefault('target', {}) policy_dict['target'].setdefault(self.member_name, {}) policy_dict['target'][self.member_name]['user_id'] = ( token_ref.user_id) try: user_domain_id = token_ref.user_domain_id except exception.UnexpectedError: user_domain_id = None if user_domain_id: policy_dict['target'][self.member_name].setdefault( 'user', {}) policy_dict['target'][ self.member_name]['user'].setdefault('domain', {}) policy_dict['target'][ self.member_name]['user']['domain']['id'] = ( user_domain_id) # Add in the kwargs, which means that any entity provided as a # parameter for calls like create and update will be included. policy_dict.update(kwargs) self.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted') return f(self, request, *args, **kwargs)
def inner(self, context, *args, **kwargs): if 'is_admin' in context and context['is_admin']: LOG.warning(_LW('RBAC: Bypassing authorization')) elif callback is not None: prep_info = {'f_name': f.__name__, 'input_attr': kwargs} callback(self, context, prep_info, *args, **kwargs) else: action = 'identity:%s' % f.__name__ creds = _build_policy_check_credentials(self, action, context, kwargs) policy_dict = {} # Check to see if we need to include the target entity in our # policy checks. We deduce this by seeing if the class has # specified a get_member() method and that kwargs contains the # appropriate entity id. if (hasattr(self, 'get_member_from_driver') and self.get_member_from_driver is not None): key = '%s_id' % self.member_name if key in kwargs: ref = self.get_member_from_driver(kwargs[key]) policy_dict['target'] = {self.member_name: ref} # TODO(henry-nash): Move this entire code to a member # method inside v3 Auth if context.get('subject_token_id') is not None: token_ref = token_model.KeystoneToken( token_id=context['subject_token_id'], token_data=self.token_provider_api.validate_token( context['subject_token_id'])) policy_dict.setdefault('target', {}) policy_dict['target'].setdefault(self.member_name, {}) policy_dict['target'][self.member_name]['user_id'] = ( token_ref.user_id) try: user_domain_id = token_ref.user_domain_id except exception.UnexpectedError: user_domain_id = None if user_domain_id: policy_dict['target'][self.member_name].setdefault( 'user', {}) policy_dict['target'][self.member_name][ 'user'].setdefault('domain', {}) policy_dict['target'][self.member_name]['user'][ 'domain']['id'] = ( user_domain_id) # Add in the kwargs, which means that any entity provided as a # parameter for calls like create and update will be included. policy_dict.update(kwargs) self.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted') return f(self, context, *args, **kwargs)
def wrapper(self, request, **kwargs): request.assert_authenticated() if not request.context.is_admin: # 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), which although most of our APIs do not utilize, # in theory you could have. # # First build the dict of filter parameters target = dict() if filters: for item in filters: if item in request.params: target[item] = request.params[item] LOG.debug('RBAC: Adding query filter params (%s)', (', '.join([ '%s=%s' % (item, target[item]) for item in target ]))) 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, request, 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, request.context_dict, 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, request, filters, **kwargs)
def wrapper(self, request, **kwargs): request.assert_authenticated() if not request.context.is_admin: # 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), which although most of our APIs do not utilize, # in theory you could have. # # First build the dict of filter parameters target = dict() if filters: for item in filters: if item in request.params: target[item] = request.params[item] LOG.debug('RBAC: Adding query filter params (%s)', ( ', '.join(['%s=%s' % (item, target[item]) for item in target]))) 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, request, 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, request.context_dict, 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, request, filters, **kwargs)
def inner(self, context, *args, **kwargs): if 'is_admin' in context and context['is_admin']: LOG.warning(_('RBAC: Bypassing authorization')) elif callback is not None: prep_info = {'f_name': f.__name__, 'input_attr': kwargs} callback(self, context, prep_info, *args, **kwargs) else: action = 'identity:%s' % f.__name__ creds = _build_policy_check_credentials(self, action, context, kwargs) policy_dict = {} # Check to see if we need to include the target entity in our # policy checks. We deduce this by seeing if the class has # specified a get_member() method and that kwargs contains the # appropriate entity id. if (hasattr(self, 'get_member_from_driver') and self.get_member_from_driver is not None): key = '%s_id' % self.member_name if key in kwargs: ref = self.get_member_from_driver(kwargs[key]) policy_dict['target'] = {self.member_name: ref} # TODO(henry-nash): Move this entire code to a member # method inside v3 Auth if context.get('subject_token_id') is not None: token_ref = self.token_api.get_token( context['subject_token_id']) policy_dict.setdefault('target', {}) policy_dict['target'].setdefault(self.member_name, {}) policy_dict['target'][self.member_name]['user_id'] = ( token_ref['user_id']) if 'domain' in token_ref['user']: policy_dict['target'][self.member_name].setdefault( 'user', {}) policy_dict['target'][self.member_name][ 'user'].setdefault('domain', {}) policy_dict['target'][self.member_name]['user'][ 'domain']['id'] = ( token_ref['user']['domain']['id']) # Add in the kwargs, which means that any entity provided as a # parameter for calls like create and update will be included. policy_dict.update(kwargs) self.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug(_('RBAC: Authorization granted')) return f(self, context, *args, **kwargs)
def inner(self, context, *args, **kwargs): if "is_admin" in context and context["is_admin"]: LOG.warning(_LW("RBAC: Bypassing authorization")) elif callback is not None: prep_info = {"f_name": f.__name__, "input_attr": kwargs} callback(self, context, prep_info, *args, **kwargs) else: action = "identity:%s" % f.__name__ creds = _build_policy_check_credentials(self, action, context, kwargs) policy_dict = {} # Check to see if we need to include the target entity in our # policy checks. We deduce this by seeing if the class has # specified a get_member() method and that kwargs contains the # appropriate entity id. if hasattr(self, "get_member_from_driver") and self.get_member_from_driver is not None: key = "%s_id" % self.member_name if key in kwargs: ref = self.get_member_from_driver(kwargs[key]) policy_dict["target"] = {self.member_name: ref} # TODO(henry-nash): Move this entire code to a member # method inside v3 Auth if context.get("subject_token_id") is not None: token_ref = token_model.KeystoneToken( token_id=context["subject_token_id"], token_data=self.token_provider_api.validate_token(context["subject_token_id"]), ) policy_dict.setdefault("target", {}) policy_dict["target"].setdefault(self.member_name, {}) policy_dict["target"][self.member_name]["user_id"] = token_ref.user_id try: user_domain_id = token_ref.user_domain_id except exception.UnexpectedError: user_domain_id = None if user_domain_id: policy_dict["target"][self.member_name].setdefault("user", {}) policy_dict["target"][self.member_name]["user"].setdefault("domain", {}) policy_dict["target"][self.member_name]["user"]["domain"]["id"] = user_domain_id # Add in the kwargs, which means that any entity provided as a # parameter for calls like create and update will be included. policy_dict.update(kwargs) self.policy_api.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug("RBAC: Authorization granted") return f(self, context, *args, **kwargs)
def inner(self, context, *args, **kwargs): if 'is_admin' in context and context['is_admin']: LOG.warning(_LW('RBAC: Bypassing authorization')) elif callback is not None: prep_info = {'f_name': f.__name__, 'input_attr': kwargs} callback(self, context, prep_info, *args, **kwargs) else: action = ('keystone', f.__name__) creds = _build_policy_check_credentials(self, action, context, kwargs) target = {} # Check to see if we need to include the target entity in our # policy checks. We deduce this by seeing if the class has # specified a get_member() method and that kwargs contains the # appropriate entity id. if (hasattr(self, 'get_member_from_driver') and self.get_member_from_driver is not None): key = '%s_id' % self.member_name if key in kwargs: ref = self.get_member_from_driver(kwargs[key]) target['obj'] = {self.member_name: ref} # Add in the kwargs, which means reqBody in create and # update operations. subParams = {} for k in kwargs.iterkeys(): if isinstance(kwargs[k], dict): subParams['reqBody.'+k] = kwargs[k] else: subParams['url.'+k] = kwargs[k] target.update(subParams) target = utils.flatten_dict(target) self.policy_api.enforce(action, target, creds) return f(self, context, *args, **kwargs)
def wrapper(self, context, **kwargs): if 'is_admin' in context and context['is_admin']: LOG.warning(_LW('RBAC: Bypassing authorization')) elif callback is not None: prep_info = {'f_name': f.__name__, 'input_attr': kwargs} callback(self, context, prep_info, *filters, **kwargs) else: action = ('keystone', f.__name__) creds = _build_policy_check_credentials(self, action, context, kwargs) # Now, build the target dict for policy check. We 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 # # First any query filter parameters target = dict() if filters: for item in filters: if item in context['query_string']: target['qStr.'+item] = context['query_string'][item] LOG.debug('RBAC: Adding query filter params (%s)', ( ', '.join(['%s=%s' % (item, target[item]) for item in target]))) # Now any formal url parameters for key in kwargs: target['url.'+key] = kwargs[key] target = utils.flatten_dict(target) self.policy_api.enforce(action, target, creds) return f(self, context, filters, **kwargs)
def _check_list_sys_roles(self, context, prep_info, *filters, **kwargs): if not 'query_string' in context: raise exception.MissingQueryString(query='domain_id') elif not 'domain_id' in context['query_string']: raise exception.MissingQueryString(query='domain_id') elif context['query_string']['domain_id'] == (CONF.role. sys_role_domain_id): prep_info['f_name'] = 'list_sys_roles' action = ('keystone', prep_info['f_name']) creds = controller._build_policy_check_credentials(self, action, context, kwargs) target = dict() for item in filters: if item in context['query_string']: target['qStr.'+item] = context['query_string'][item] for key in kwargs: target['url.'+key] = kwargs[key] target = utils.flatten_dict(target) self.policy_api.enforce(action, target, creds) LOG.debug('RBAC: Authorization granted')
def enforce_call(cls, enforcer=None, action=None, target_attr=None, member_target_type=None, member_target=None, filters=None, build_target=None): """Enforce RBAC on the current request. This will do some legwork and then instantiate the Enforcer if an enforcer is not passed in. :param enforcer: A pre-instantiated Enforcer object (optional) :type enforcer: :class:`RBACEnforcer` :param action: the name of the rule/policy enforcement to be checked against, e.g. `identity:get_user` (optional may be replaced by decorating the method/function with `policy_enforcer_action`. :type action: str :param target_attr: complete override of the target data. This will replace all other generated target data meaning `member_target_type` and `member_target` are ignored. This will also prevent extraction of data from the X-Subject-Token. The `target` dict should contain a series of key-value pairs such as `{'user': user_ref_dict}`. :type target_attr: dict :param member_target_type: the type of the target, e.g. 'user'. Both this and `member_target` must be passed if either is passed. :type member_target_type: str :param member_target: the (dict form) reference of the member object. Both this and `member_target_type` must be passed if either is passed. :type member_target: dict :param filters: A variable number of optional string filters, these are used to extract values from the query params. The filters are added to the reques data that is passed to the enforcer and may be used to determine policy action. In practice these are mainly supplied in the various "list" APIs and are un-used in the default supplied policies. :type filters: iterable :param build_target: A function to build the target for enforcement. This is explicitly done after authentication in order to not leak existance data before auth. :type build_target: function """ # NOTE(morgan) everything in the policy_dict may be used by the policy # DSL to action on RBAC and request information/response data. policy_dict = {} # If "action" has not explicitly been overridden, see if it is set in # Flask.g app-context (per-request thread local) meaning the # @policy_enforcer_action decorator was used. action = action or getattr(flask.g, cls.ACTION_STORE_ATTR, None) if action not in _POSSIBLE_TARGET_ACTIONS: LOG.warning('RBAC: Unknown enforcement action name `%s`. ' 'Rejecting as Forbidden, this is a programming error ' 'and a bug should be filed with as much information ' 'about the request that caused this as possible.', action) # NOTE(morgan): While this is an internal error, a 500 is never # desirable, we have handled the case and the most appropriate # response here is to issue a 403 (FORBIDDEN) to any API calling # enforce_call with an inappropriate action/name to look up the # policy rule. This is simply a short-circuit as the enforcement # code raises a 403 on an unknown action (in keystone) by default. raise exception.Forbidden( message=_( 'Internal RBAC enforcement error, invalid rule (action) ' 'name.')) # Mark flask.g as "enforce_call" has been called. This should occur # before anything except the "is this a valid action" check, ensuring # all proper "after request" checks pass, showing that the API has # enforcement. setattr(flask.g, _ENFORCEMENT_CHECK_ATTR, True) # Assert we are actually authenticated cls._assert_is_authenticated() # Check if "is_admin", this is in support of the old "admin auth token" # middleware with a shared "admin" token for auth if cls._shared_admin_auth_token_set(): LOG.warning('RBAC: Bypassing authorization') return # NOTE(morgan): !!! ORDER OF THESE OPERATIONS IS IMPORTANT !!! # The lowest priority values are set first and the highest priority # values are set last. # Populate the input attributes (view args) directly to the policy # dict. This is to allow the policy engine to have access to the # view args for substitution. This is to mirror the old @protected # mechanism and ensure current policy files continue to work as # expected. policy_dict.update(flask.request.view_args) # Get the Target Data Set. if target_attr is None and build_target is None: try: policy_dict.update(cls._extract_member_target_data( member_target_type, member_target)) except exception.NotFound: # DEBUG LOG and bubble up the 404 error. This is expected # behavior. This likely should be specific in each API. This # should be revisited in the future and each API should make # the explicit "existence" checks before enforcement. LOG.debug('Extracting inferred target data resulted in ' '"NOT FOUND (404)".') raise except Exception as e: # nosec # NOTE(morgan): Errors should never bubble up at this point, # if there is an error getting the target, log it and move # on. Raise an explicit 403, we have failed policy checks. LOG.warning('Unable to extract inferred target data during ' 'enforcement') LOG.debug(e, exc_info=True) raise exception.ForbiddenAction(action=action) # Special Case, extract and add subject_token data. subj_token_target_data = cls._extract_subject_token_target_data() if subj_token_target_data: policy_dict.setdefault('target', {}).update( subj_token_target_data) else: if target_attr and build_target: raise ValueError('Programming Error: A target_attr or ' 'build_target must be provided, but not both') policy_dict['target'] = target_attr or build_target() # Pull the data from the submitted json body to generate # appropriate input/target attributes, we take an explicit copy here # to ensure we're not somehow corrupting json_input = flask.request.get_json(force=True, silent=True) or {} policy_dict.update(json_input.copy()) # Generate the filter_attr dataset. policy_dict.update(cls._extract_filter_values(filters)) flattened = utils.flatten_dict(policy_dict) if LOG.logger.getEffectiveLevel() <= log.DEBUG: # LOG the Args args_str = ', '.join( ['%s=%s' % (k, v) for k, v in (flask.request.view_args or {}).items()]) args_str = strutils.mask_password(args_str) LOG.debug('RBAC: Authorizing `%(action)s(%(args)s)`', {'action': action, 'args': args_str}) ctxt = cls._get_oslo_req_context() # Instantiate the enforcer object if needed. enforcer_obj = enforcer or cls() enforcer_obj._enforce( credentials=ctxt, action=action, target=flattened) LOG.debug('RBAC: Authorization granted')
def enforce_call(cls, enforcer=None, action=None, target_attr=None, member_target_type=None, member_target=None, filters=None): """Enforce RBAC on the current request. This will do some legwork and then instantiate the Enforcer if an enforcer is not passed in. :param enforcer: A pre-instantiated Enforcer object (optional) :type enforcer: :class:`RBACEnforcer` :param action: the name of the rule/policy enforcement to be checked against, e.g. `identity:get_user` (optional may be replaced by decorating the method/function with `policy_enforcer_action`. :type action: str :param target_attr: complete override of the target data. This will replace all other generated target data meaning `member_target_type` and `member_target` are ignored. This will also prevent extraction of data from the X-Subject-Token. The `target` dict should contain a series of key-value pairs such as `{'user': user_ref_dict}`. :type target_attr: dict :param member_target_type: the type of the target, e.g. 'user'. Both this and `member_target` must be passed if either is passed. :type member_target_type: str :param member_target: the (dict form) reference of the member object. Both this and `member_target_type` must be passed if either is passed. :type member_target: dict :param filters: A variable number of optional string filters, these are used to extract values from the query params. The filters are added to the reques data that is passed to the enforcer and may be used to determine policy action. In practice these are mainly supplied in the various "list" APIs and are un-used in the default supplied policies. :type filters: iterable """ # NOTE(morgan) everything in the policy_dict may be used by the policy # DSL to action on RBAC and request information/response data. policy_dict = {} # If "action" has not explicitly been overridden, see if it is set in # Flask.g app-context (per-request thread local) meaning the # @policy_enforcer_action decorator was used. action = action or getattr(flask.g, cls.ACTION_STORE_ATTR, None) if action not in _POSSIBLE_TARGET_ACTIONS: LOG.warning( 'RBAC: Unknown enforcement action name `%s`. ' 'Rejecting as Forbidden, this is a programming error ' 'and a bug should be filed with as much information ' 'about the request that caused this as possible.', action) # NOTE(morgan): While this is an internal error, a 500 is never # desirable, we have handled the case and the most appropriate # response here is to issue a 403 (FORBIDDEN) to any API calling # enforce_call with an inappropriate action/name to look up the # policy rule. This is simply a short-circuit as the enforcement # code raises a 403 on an unknown action (in keystone) by default. raise exception.Forbidden(message=_( 'Internal RBAC enforcement error, invalid rule (action) ' 'name.')) # Mark flask.g as "enforce_call" has been called. This should occur # before anything except the "is this a valid action" check, ensuring # all proper "after request" checks pass, showing that the API has # enforcement. setattr(flask.g, _ENFORCEMENT_CHECK_ATTR, True) # Assert we are actually authenticated cls._assert_is_authenticated() # Check if "is_admin", this is in support of the old "admin auth token" # middleware with a shared "admin" token for auth if cls._shared_admin_auth_token_set(): LOG.warning('RBAC: Bypassing authorization') return # NOTE(morgan): !!! ORDER OF THESE OPERATIONS IS IMPORTANT !!! # The lowest priority values are set first and the highest priority # values are set last. # Get the Target Data Set. if target_attr is None: policy_dict.update( cls._extract_member_target_data(member_target_type, member_target)) # Special Case, extract and add subject_token data. subj_token_target_data = cls._extract_subject_token_target_data() if subj_token_target_data: policy_dict.setdefault('target', {}).update(subj_token_target_data) else: policy_dict['target'] = target_attr # Pull the data from the view args (path based params) to generate # appropriate input/target attributes, we take an explicit copy here # to ensure we're not somehow corrupting policy_dict.update(flask.request.view_args or {}) # Generate the filter_attr dataset. policy_dict.update(cls._extract_filter_values(filters)) # Extract the cred data creds = cls._extract_policy_check_credentials() flattened = utils.flatten_dict(policy_dict) if LOG.logger.getEffectiveLevel() <= log.DEBUG: # LOG the Args args_str = ', '.join([ '%s=%s' % (k, v) for k, v in (flask.request.view_args or {}).items() ]) args_str = strutils.mask_password(args_str) LOG.debug('RBAC: Authorizing `%(action)s(%(args)s)`', { 'action': action, 'args': args_str }) # LOG the Cred Data cred_str = ', '.join(['%s=%s' % (k, v) for k, v in creds.items()]) cred_str = strutils.mask_password(cred_str) LOG.debug( 'RBAC: Policy Enforcement Cred Data ' '`%(action)s creds(%(cred_str)s)`', { 'action': action, 'cred_str': cred_str }) # Log the Target Data target_str = ', '.join( ['%s=%s' % (k, v) for k, v in flattened.items()]) target_str = strutils.mask_password(target_str) LOG.debug( 'RBAC: Policy Enforcement Target Data ' '`%(action)s => target(%(target_str)s)`', { 'action': action, 'target_str': target_str }) # Instantiate the enforcer object if needed. enforcer_obj = enforcer or cls() enforcer_obj._enforce(credentials=creds, action=action, target=flattened) LOG.debug('RBAC: Authorization granted')