def assert_admin(self, request): """Ensure the user is an admin. :raises keystone.exception.Unauthorized: if a token could not be found/authorized, a user is invalid, or a tenant is invalid/not scoped. :raises keystone.exception.Forbidden: if the user is not an admin and does not have the admin role """ request.assert_authenticated() if not request.context.is_admin: user_token_ref = utils.get_token_ref(request.context_dict) creds = copy.deepcopy(user_token_ref.metadata) try: creds['user_id'] = user_token_ref.user_id except exception.UnexpectedError: LOG.debug('Invalid user') raise exception.Unauthorized(_('Invalid user')) if user_token_ref.project_scoped: creds['tenant_id'] = user_token_ref.project_id else: LOG.debug('Invalid tenant') raise exception.Unauthorized(_('Invalid tenant')) creds['roles'] = user_token_ref.role_names # Accept either is_admin or the admin role policy.enforce(creds, 'admin_required', {})
def enforce(credentials, action, target): """Verifies that the action is valid on the target in this context. :param credentials: user credentials :param action: string representing the action to be checked, which should be colon separated for clarity. :param target: dictionary representing the object of the action for object creation this should be a dictionary representing the location of the object e.g. {'tenant_id': object.tenant_id} :raises: `exception.Forbidden` if verification fails. Actions should be colon separated for clarity. For example: * compute:create_instance * compute:attach_volume * volume:attach_volume """ init() match_list = ('rule:%s' % action,) try: common_policy.enforce(match_list, target, credentials) except common_policy.NotAuthorized: raise exception.ForbiddenAction(action=action)
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('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']) policy.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted')
def enforce(credentials, action, target): """Verifies that the action is valid on the target in this context. :param credentials: user credentials :param action: string representing the action to be checked this should be colon separated for clarity. i.e. compute:create_instance compute:attach_volume volume:attach_volume :param object: dictionary representing the object of the action for object creation this should be a dictionary representing the location of the object e.g. {'tenant_id': object.tenant_id} :raises: `exception.Forbidden` if verification fails. """ init() match_list = ('rule:%s' % action, ) try: common_policy.enforce(match_list, target, credentials) except common_policy.NotAuthorized: raise exception.ForbiddenAction(action=action)
def test_ignore_case_role_check(self): lowercase_action = "example:lowercase_admin" uppercase_action = "example:uppercase_admin" # NOTE(dprince): We mix case in the Admin role here to ensure # case is ignored admin_credentials = {'roles': ['AdMiN']} policy.enforce(admin_credentials, lowercase_action, self.target) policy.enforce(admin_credentials, uppercase_action, self.target)
def test_templatized_enforcement(self): target_mine = {'project_id': 'fake'} target_not_mine = {'project_id': 'another'} credentials = {'project_id': 'fake', 'roles': []} action = "example:my_file" policy.enforce(credentials, action, target_mine) self.assertRaises(exception.ForbiddenAction, policy.enforce, credentials, action, target_not_mine)
def inner(self, request, *args, **kwargs): request.assert_authenticated() if request.context.is_admin: LOG.warning('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) policy.enforce(creds, action, utils.flatten_dict(policy_dict)) LOG.debug('RBAC: Authorization granted') return f(self, request, *args, **kwargs)
def test_warning_message_is_logged_if_enforce_scope_is_false(self): self.config_fixture.config(group='oslo_policy', enforce_scope=False) expected_msg = ( 'Policy foo failed scope check. The token used to make the ' 'request was project scoped but the policy requires [\'system\'] ' 'scope. This behavior may change in the future where using the ' 'intended scope is required') with mock.patch('warnings.warn') as mock_warn: policy.enforce(self.credentials, self.action, self.target) mock_warn.assert_called_once_with(expected_msg)
def test_warning_message_is_logged_if_enforce_scope_is_false(self): self.config_fixture.config(group='oslo_policy', enforce_scope=False) expected_msg = ( 'Policy foo failed scope check. The token used to make the ' 'request was project scoped but the policy requires [\'system\'] ' 'scope. This behavior may change in the future where using the ' 'intended scope is required' ) with mock.patch('warnings.warn') as mock_warn: policy.enforce(self.credentials, self.action, self.target) mock_warn.assert_called_once_with(expected_msg)
def test_modified_policy_reloads(self): action = "example:test" empty_credentials = {} with open(self.tmpfilename, "w") as policyfile: policyfile.write("""{"example:test": []}""") policy.enforce(empty_credentials, action, self.target) with open(self.tmpfilename, "w") as policyfile: policyfile.write("""{"example:test": ["false:false"]}""") policy._ENFORCER.clear() self.assertRaises(exception.ForbiddenAction, policy.enforce, empty_credentials, action, self.target)
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] policy.enforce(creds, action, utils.flatten_dict(target)) LOG.debug('RBAC: Authorization granted') else: LOG.warning('RBAC: Bypassing authorization') return f(self, request, filters, **kwargs)
def test_enforce_good_action(self): action = "example:allowed" policy.enforce(self.credentials, action, self.target)
def test_not_found_policy_calls_default(self): policy.enforce(self.credentials, "example:noexist", {})
def test_early_OR_enforcement(self): action = "example:early_or_success" policy.enforce(self.credentials, action, self.target)
def enforce(self, credentials, action, target): msg = 'enforce %(action)s: %(credentials)s' LOG.debug(msg, { 'action': action, 'credentials': credentials}) policy.enforce(credentials, action, target)