def _build_policy_check_credentials(self, action, context, kwargs): kwargs_str = ', '.join(['%s=%s' % (k, kwargs[k]) for k in kwargs]) kwargs_str = strutils.mask_password(kwargs_str) LOG.debug('RBAC: Authorizing %(action)s(%(kwargs)s)', { 'action': action, 'kwargs': kwargs_str }) # see if auth context has already been created. If so use it. if ('environment' in context and authorization.AUTH_CONTEXT_ENV in context['environment']): LOG.debug('RBAC: using auth context from the request environment') return context['environment'].get(authorization.AUTH_CONTEXT_ENV) # There is no current auth context, build it from the incoming token. # TODO(morganfainberg): Collapse this logic with AuthContextMiddleware # in a sane manner as this just mirrors the logic in AuthContextMiddleware try: LOG.debug('RBAC: building auth context from the incoming auth token') token_ref = token_model.KeystoneToken( token_id=context['token_id'], token_data=self.token_provider_api.validate_token( context['token_id'])) # NOTE(jamielennox): whilst this maybe shouldn't be within this # function it would otherwise need to reload the token_ref from # backing store. wsgi.validate_token_bind(context, token_ref) except exception.TokenNotFound: LOG.warning(_LW('RBAC: Invalid token')) raise exception.Unauthorized() auth_context = authorization.token_to_auth_context(token_ref) return auth_context
def _build_auth_context(self, request): token_id = request.headers.get(AUTH_TOKEN_HEADER).strip() if token_id == CONF.admin_token: # NOTE(gyee): no need to proceed any further as the special admin # token is being handled by AdminTokenAuthMiddleware. This code # will not be impacted even if AdminTokenAuthMiddleware is removed # from the pipeline as "is_admin" is default to "False". This code # is independent of AdminTokenAuthMiddleware. return {} context = {'token_id': token_id} context['environment'] = request.environ try: token_ref = token_model.KeystoneToken( token_id=token_id, token_data=self.token_provider_api.validate_token(token_id)) # TODO(gyee): validate_token_bind should really be its own # middleware wsgi.validate_token_bind(context, token_ref) return authorization.token_to_auth_context(token_ref) except exception.TokenNotFound: LOG.warning(_('RBAC: Invalid token')) raise exception.Unauthorized()
def _create_base_saml_assertion(self, context, auth): issuer = CONF.saml.idp_entity_id sp_id = auth['scope']['service_provider']['id'] service_provider = self.federation_api.get_sp(sp_id) utils.assert_enabled_service_provider_object(service_provider) sp_url = service_provider['sp_url'] token_id = auth['identity']['token']['id'] token_data = self.token_provider_api.validate_token(token_id) token_ref = token_model.KeystoneToken(token_id, token_data) if not token_ref.project_scoped: action = _('Use a project scoped token when attempting to create ' 'a SAML assertion') raise exception.ForbiddenAction(action=action) subject = token_ref.user_name roles = token_ref.role_names project = token_ref.project_name # NOTE(rodrigods): the domain name is necessary in order to distinguish # between projects and users with the same name in different domains. project_domain_name = token_ref.project_domain_name subject_domain_name = token_ref.user_domain_name generator = keystone_idp.SAMLGenerator() response = generator.samlize_token(issuer, sp_url, subject, subject_domain_name, roles, project, project_domain_name) return (response, service_provider)
def assert_kerberos_bind(self, tokens, bind_level, use_kerberos=True, success=True): if not isinstance(tokens, dict): for token in tokens: self.assert_kerberos_bind(token, bind_level, use_kerberos=use_kerberos, success=success) elif use_kerberos == ANY: for val in (True, False): self.assert_kerberos_bind(tokens, bind_level, use_kerberos=val, success=success) else: context = {'environment': {}} self.config_fixture.config(group='token', enforce_token_bind=bind_level) if use_kerberos: context['environment']['REMOTE_USER'] = KERBEROS_BIND context['environment']['AUTH_TYPE'] = 'Negotiate' # NOTE(morganfainberg): This assumes a V3 token. token_ref = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=tokens) if not success: self.assertRaises(exception.Unauthorized, wsgi.validate_token_bind, context, token_ref) else: wsgi.validate_token_bind(context, token_ref)
def test_token_model_v2_federated_user(self): token_data = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=self.v2_sample_token) federation_data = { 'identity_provider': { 'id': uuid.uuid4().hex }, 'protocol': { 'id': 'saml2' }, 'groups': [{ 'id': uuid.uuid4().hex } for x in range(1, 5)] } self.assertFalse(token_data.is_federated_user) self.assertEqual([], token_data.federation_group_ids) self.assertIsNone(token_data.federation_protocol_id) self.assertIsNone(token_data.federation_idp_id) token_data['user'][federation_constants.FEDERATION] = federation_data # Federated users should not exist in V2, the data should remain empty self.assertFalse(token_data.is_federated_user) self.assertEqual([], token_data.federation_group_ids) self.assertIsNone(token_data.federation_protocol_id) self.assertIsNone(token_data.federation_idp_id)
def test_token_model_v3_federated_user(self): token_data = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=self.v3_sample_token) federation_data = { 'identity_provider': { 'id': uuid.uuid4().hex }, 'protocol': { 'id': 'saml2' }, 'groups': [{ 'id': uuid.uuid4().hex } for x in range(1, 5)] } self.assertFalse(token_data.is_federated_user) self.assertEqual([], token_data.federation_group_ids) self.assertIsNone(token_data.federation_protocol_id) self.assertIsNone(token_data.federation_idp_id) token_data['user'][federation_constants.FEDERATION] = federation_data self.assertTrue(token_data.is_federated_user) self.assertEqual([x['id'] for x in federation_data['groups']], token_data.federation_group_ids) self.assertEqual(federation_data['protocol']['id'], token_data.federation_protocol_id) self.assertEqual(federation_data['identity_provider']['id'], token_data.federation_idp_id)
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 _get_user_id(self, context): if 'token_id' in context: token_id = context['token_id'] token_data = self.token_provider_api.validate_token(token_id) token_ref = token_model.KeystoneToken(token_id=token_id, token_data=token_data) return token_ref.user_id return None
def fill_context(self, request): # The request context stores itself in thread-local memory for logging. if authorization.AUTH_CONTEXT_ENV in request.environ: msg = ('Auth context already exists in the request ' 'environment; it will be used for authorization ' 'instead of creating a new one.') LOG.warning(msg) return kwargs = {'authenticated': False, 'overwrite': True} request_context = context.RequestContext.from_environ( request.environ, **kwargs) request.environ[context.REQUEST_CONTEXT_ENV] = request_context # NOTE(gyee): token takes precedence over SSL client certificates. # This will preserve backward compatibility with the existing # behavior. Tokenless authorization with X.509 SSL client # certificate is effectively disabled if no trusted issuers are # provided. if request.environ.get(wsgi.CONTEXT_ENV, {}).get('is_admin', False): request_context.is_admin = True auth_context = {} elif request.token_auth.has_user_token: # Keystone enforces policy on some values that other services # do not, and should not, use. This adds them in to the context. token = token_model.KeystoneToken(token_id=request.user_token, token_data=request.token_info) self._keystone_specific_values(token, request_context) request_context.auth_token = request.user_token auth_context = request_context.to_policy_values() additional = { 'trust_id': request_context.trust_id, 'trustor_id': request_context.trustor_id, 'trustee_id': request_context.trustee_id, 'domain_id': request_context._domain_id, 'domain_name': request_context.domain_name, 'group_ids': request_context.group_ids, 'token': token } auth_context.update(additional) elif self._validate_trusted_issuer(request): auth_context = self._build_tokenless_auth_context(request) else: # There is either no auth token in the request or the certificate # issuer is not trusted. No auth context will be set. This # typically happens on an initial token request. return # set authenticated to flag to keystone that a token has been validated request_context.authenticated = True LOG.debug('RBAC: auth_context: %s', auth_context) request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
def test_oauth_variables_not_set(self): token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertIsNone(auth_context['access_token_id']) self.assertIsNone(auth_context['consumer_id'])
def callback(self, context, prep_info, *args, **kwargs): token_ref = "" if context.get('token_id') is not None: token_ref = token_model.KeystoneToken( token_id=context['token_id'], token_data=self.token_provider_api.validate_token( context['token_id'])) if not token_ref: raise exception.Unauthorized
def delete_consumer(self, context, consumer_id): user_token_ref = token_model.KeystoneToken( token_id=context['token_id'], token_data=self.token_provider_api.validate_token( context['token_id'])) payload = {'user_id': user_token_ref.user_id, 'consumer_id': consumer_id} _emit_user_oauth_consumer_token_invalidate(payload) self.oauth_api.delete_consumer(consumer_id)
def delete_consumer(self, context, consumer_id): user_token_ref = token_model.KeystoneToken( token_id=context['token_id'], token_data=self.token_provider_api.validate_token( context['token_id'])) payload = {'user_id': user_token_ref.user_id, 'consumer_id': consumer_id} _emit_user_oauth_consumer_token_invalidate(payload) initiator = notifications._get_request_audit_info(context) self.oauth_api.delete_consumer(consumer_id, initiator)
def _get_token_ref(): """ For getting the token """ LOG.info("inside get token ref") token_id = id response = self.token_provider_api.validate_token(token_id) return token_model.KeystoneToken(token_id=token_id, token_data=response)
def set_user_password(self, request, user_id, user): token_id = request.context_dict.get('token_id') original_password = user.get('original_password') token_data = self.token_provider_api.validate_token(token_id) token_ref = token_model.KeystoneToken(token_id=token_id, token_data=token_data) if token_ref.user_id != user_id: raise exception.Forbidden('Token belongs to another user') if original_password is None: raise exception.ValidationError(target='user', attribute='original password') try: user_ref = self.identity_api.authenticate( request, user_id=token_ref.user_id, password=original_password) if not user_ref.get('enabled', True): # NOTE(dolph): why can't you set a disabled user's password? raise exception.Unauthorized('User is disabled') except AssertionError: raise exception.Unauthorized( _('v2.0 password change call failed ' 'due to rejected authentication')) update_dict = {'password': user['password'], 'id': user_id} old_admin = request.context.is_admin request.context.is_admin = True super(UserController, self).set_user_password(request, user_id, update_dict) request.context.is_admin = old_admin # Issue a new token based upon the original token data. This will # always be a V2.0 token. # NOTE(lbragstad): Since we just updated the password and presisted a # revocation event for the user changing the password, it is necessary # to wait one second before authenticating. This ensures we are in the # threshold of a new second before getting a new token. import time time.sleep(1) new_token_id, new_token_data = self.token_provider_api.issue_token( token_ref.user_id, token_ref.methods, project_id=token_ref.project_id, parent_audit_id=token_ref.audit_chain_id) v2_helper = token_controllers.V2TokenDataHelper() v2_token_data = v2_helper.v3_to_v2_token(new_token_data, new_token_id) LOG.debug('TOKEN_REF %s', new_token_data) return v2_token_data
def test_user_id_missing_in_token_raises_exception(self): # If there's no user ID in the token then an Unauthorized # exception is raised. token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) del token_data['token']['user']['id'] token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) self.assertRaises(exception.Unauthorized, authorization.token_to_auth_context, token)
def endpoints(self, request, token_id): """Return a list of endpoints available to the token.""" self.assert_admin(request) token_data = self.token_provider_api.validate_token(token_id) token_ref = token_model.KeystoneToken(token_id, token_data) catalog_ref = None if token_ref.project_id: catalog_ref = self.catalog_api.get_catalog(token_ref.user_id, token_ref.project_id) return Auth.format_endpoint_list(catalog_ref)
def test_token_model_dual_scoped_token(self): domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex} self.v2_sample_token['access']['domain'] = domain self.v3_sample_token['token']['domain'] = domain # V2 Tokens Cannot be domain scoped, this should work token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=self.v2_sample_token) self.assertRaises(exception.UnexpectedError, token_model.KeystoneToken, token_id=uuid.uuid4().hex, token_data=self.v3_sample_token)
def test_token_is_unscoped(self): # Check contents of auth_context when the token is unscoped. token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) del token_data['token']['project'] token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertNotIn('project_id', auth_context) self.assertNotIn('domain_id', auth_context) self.assertNotIn('domain_name', auth_context)
def test_oauth_variables_set_for_oauth_token(self): token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) access_token_id = uuid.uuid4().hex consumer_id = uuid.uuid4().hex token_data['token']['OS-OAUTH1'] = { 'access_token_id': access_token_id, 'consumer_id': consumer_id } token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertEqual(access_token_id, auth_context['access_token_id']) self.assertEqual(consumer_id, auth_context['consumer_id'])
def revoke_token(self, token_id, revoke_chain=False): token_ref = token_model.KeystoneToken( token_id=token_id, token_data=self.validate_token(token_id)) project_id = token_ref.project_id if token_ref.project_scoped else None domain_id = token_ref.domain_id if token_ref.domain_scoped else None if revoke_chain: self.revoke_api.revoke_by_audit_chain_id(token_ref.audit_chain_id, project_id=project_id, domain_id=domain_id) else: self.revoke_api.revoke_by_audit_id(token_ref.audit_id) if CONF.token.revoke_by_id and self._needs_persistence: self._persistence.delete_token(token_id=token_id)
def _get_token_ref(self, token_id, belongs_to=None): """Return a token if a valid one exists. Optionally, limited to a token owned by a specific tenant. """ token_ref = token_model.KeystoneToken( token_id=token_id, token_data=self.token_provider_api.validate_token(token_id)) if belongs_to: if not token_ref.project_scoped: raise exception.Unauthorized( _('Token does not belong to specified tenant.')) if token_ref.project_id != belongs_to: raise exception.Unauthorized( _('Token does not belong to specified tenant.')) return token_ref
def test_token_is_domain_scoped(self): # Check contents of auth_context when token is domain-scoped. token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) del token_data['token']['project'] domain_id = uuid.uuid4().hex domain_name = uuid.uuid4().hex token_data['token']['domain'] = {'id': domain_id, 'name': domain_name} token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertNotIn('project_id', auth_context) self.assertEqual(domain_id, auth_context['domain_id']) self.assertEqual(domain_name, auth_context['domain_name'])
def test_token_is_project_scoped_with_trust(self): # Check auth_context result when the token is project-scoped and has # trust info. # SAMPLE_V3_TOKEN has OS-TRUST:trust in it. token_data = test_token_provider.SAMPLE_V3_TOKEN token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertEqual(token, auth_context['token']) self.assertTrue(auth_context['is_delegated_auth']) self.assertEqual(token_data['token']['user']['id'], auth_context['user_id']) self.assertEqual(token_data['token']['user']['name'], auth_context['user_name']) self.assertEqual(token_data['token']['user']['domain']['id'], auth_context['user_domain_id']) self.assertEqual(token_data['token']['user']['domain']['name'], auth_context['user_domain_name']) self.assertEqual(token_data['token']['project']['id'], auth_context['project_id']) self.assertEqual(token_data['token']['project']['domain']['id'], auth_context['project_domain_id']) self.assertEqual(token_data['token']['project']['domain']['name'], auth_context['project_domain_name']) self.assertNotIn('domain_id', auth_context) self.assertNotIn('domain_name', auth_context) self.assertEqual(token_data['token']['OS-TRUST:trust']['id'], auth_context['trust_id']) self.assertEqual( token_data['token']['OS-TRUST:trust']['trustor_user_id'], auth_context['trustor_id']) self.assertEqual( token_data['token']['OS-TRUST:trust']['trustee_user_id'], auth_context['trustee_id']) self.assertItemsEqual( [r['name'] for r in token_data['token']['roles']], auth_context['roles']) self.assertIsNone(auth_context['consumer_id']) self.assertIsNone(auth_context['access_token_id']) self.assertNotIn('group_ids', auth_context)
def test_token_is_for_federated_user(self): # When the token is for a federated user then group_ids is in # auth_context. token_data = copy.deepcopy(test_token_provider.SAMPLE_V3_TOKEN) group_ids = [uuid.uuid4().hex for x in range(1, 5)] federation_data = {'identity_provider': {'id': uuid.uuid4().hex}, 'protocol': {'id': 'saml2'}, 'groups': [{'id': gid} for gid in group_ids]} token_data['token']['user'][federation_constants.FEDERATION] = ( federation_data) token = token_model.KeystoneToken(token_id=uuid.uuid4().hex, token_data=token_data) auth_context = authorization.token_to_auth_context(token) self.assertItemsEqual(group_ids, auth_context['group_ids'])
def _assert_identity(self, context, user_id): """Check that the provided token belongs to the user. :param context: standard context :param user_id: id of user :raises exception.Forbidden: when token is invalid """ try: token_data = self.token_provider_api.validate_token( context['token_id']) except exception.TokenNotFound as e: raise exception.Unauthorized(e) token_ref = token_model.KeystoneToken(token_id=context['token_id'], token_data=token_data) if token_ref.user_id != user_id: raise exception.Forbidden(_('Token belongs to another user'))
def revoke_token(self, token_id, revoke_chain=False): token_ref = token_model.KeystoneToken( token_id=token_id, token_data=self.validate_token(token_id)) project_id = token_ref.project_id if token_ref.project_scoped else None domain_id = token_ref.domain_id if token_ref.domain_scoped else None if revoke_chain: PROVIDERS.revoke_api.revoke_by_audit_chain_id( token_ref.audit_chain_id, project_id=project_id, domain_id=domain_id) else: PROVIDERS.revoke_api.revoke_by_audit_id(token_ref.audit_id) # FIXME(morganfainberg): Does this cache actually need to be # invalidated? We maintain a cached revocation list, which should be # consulted before accepting a token as valid. For now we will # do the explicit individual token invalidation. self.invalidate_individual_token_cache(token_id)
def revoke_token(self, token_id, revoke_chain=False): if self.revoke_api: revoke_by_expires = False project_id = None domain_id = None token_ref = token_model.KeystoneToken( token_id=token_id, token_data=self.validate_token(token_id)) user_id = token_ref.user_id expires_at = token_ref.expires audit_id = token_ref.audit_id audit_chain_id = token_ref.audit_chain_id if token_ref.project_scoped: project_id = token_ref.project_id if token_ref.domain_scoped: domain_id = token_ref.domain_id if audit_id is None and not revoke_chain: LOG.debug('Received token with no audit_id.') revoke_by_expires = True if audit_chain_id is None and revoke_chain: LOG.debug('Received token with no audit_chain_id.') revoke_by_expires = True if revoke_by_expires: self.revoke_api.revoke_by_expiration(user_id, expires_at, project_id=project_id, domain_id=domain_id) elif revoke_chain: self.revoke_api.revoke_by_audit_chain_id(audit_chain_id, project_id=project_id, domain_id=domain_id) else: self.revoke_api.revoke_by_audit_id(audit_id) if CONF.token.revoke_by_id: self._persistence.delete_token(token_id=token_id)
def _handle_subject_token_id(self, request, policy_dict): if request.subject_token is not None: window_seconds = token_validation_window(request) token_ref = token_model.KeystoneToken( token_id=request.subject_token, token_data=self.token_provider_api.validate_token( request.subject_token, 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)
def _get_domain_id_from_token(self, context): """Get the domain_id for a v3 create call. In the case of a v3 create entity call that does not specify a domain ID, the spec says that we should use the domain scoping from the token being used. """ # We could make this more efficient by loading the domain_id # into the context in the wrapper function above (since # this version of normalize_domain will only be called inside # a v3 protected call). However, this optimization is probably not # worth the duplication of state try: token_ref = token_model.KeystoneToken( token_id=context['token_id'], token_data=self.token_provider_api.validate_token( context['token_id'])) except KeyError: # This might happen if we use the Admin token, for instance raise exception.ValidationError( _('A domain-scoped token must be used')) except (exception.TokenNotFound, exception.UnsupportedTokenVersionException): LOG.warning( _LW('Invalid token found while getting domain ID ' 'for list request')) raise exception.Unauthorized() if token_ref.domain_scoped: return token_ref.domain_id else: # TODO(henry-nash): We should issue an exception here since if # a v3 call does not explicitly specify the domain_id in the # entity, it should be using a domain scoped token. However, # the current tempest heat tests issue a v3 call without this. # This is raised as bug #1283539. Once this is fixed, we # should remove the line below and replace it with an error. return CONF.identity.default_domain_id