def _get_sso_origin_host(self, request): """Validate and return originating dashboard URL. Make sure the parameter is specified in the request's URL as well its value belongs to a list of trusted dashboards. :param context: request's context :raises keystone.exception.ValidationError: ``origin`` query parameter was not specified. The URL is deemed invalid. :raises keystone.exception.Unauthorized: URL specified in origin query parameter does not exist in list of websso trusted dashboards. :returns: URL with the originating dashboard """ origin = request.params.get('origin') if not origin: msg = _('Request must have an origin query parameter') LOG.error(msg) raise exception.ValidationError(msg) host = urllib.parse.unquote_plus(origin) # change trusted_dashboard hostnames to lowercase before comparison trusted_dashboards = [ k_utils.lower_case_hostname(trusted) for trusted in CONF.federation.trusted_dashboard ] if host not in trusted_dashboards: msg = _('%(host)s is not a trusted dashboard host') msg = msg % {'host': host} LOG.error(msg) raise exception.Unauthorized(msg) return host
def _get_domain_id_for_list_request(self, context): """Get the domain_id for a v3 list call. If we running with multiple domain drivers, then the caller must specify a domain_id either as a filter or as part of the token scope. """ if not CONF.identity.domain_specific_drivers_enabled: # We don't need to specify a domain ID in this case return if context['query_string'].get('domain_id') is not None: return context['query_string'].get('domain_id') token_ref = utils.get_token_ref(context) 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')) raise exception.Unauthorized()
def lookup_user(self, user_info): user_id = user_info.get('id') user_name = user_info.get('name') user_ref = None if not user_id and not user_name: raise exception.ValidationError(attribute='id or name', target='user') try: if user_name: if 'domain' not in user_info: raise exception.ValidationError(attribute='domain', target='user') domain_ref = self._lookup_domain(user_info['domain']) user_ref = self.identity_api.get_user_by_name( context=self.context, user_name=user_name, domain_id=domain_ref['id']) else: user_ref = self.identity_api.get_user( context=self.context, user_id=user_id) except exception.UserNotFound as e: LOG.exception(e) raise exception.Unauthorized(e) self._assert_user_is_enabled(user_ref) return user_ref
def __setitem__(self, key, val): """Override __setitem__ to prevent conflicting values.""" if key in self.IDENTITY_ATTRIBUTES and key in self: existing_val = self[key] if key == 'expires_at': # special treatment for 'expires_at', we are going to take # the earliest expiration instead. if existing_val != val: LOG.info('"expires_at" has conflicting values ' '%(existing)s and %(new)s. Will use the ' 'earliest value.', {'existing': existing_val, 'new': val}) if existing_val is None or val is None: val = existing_val or val else: val = min(existing_val, val) elif existing_val != val: msg = _('Unable to reconcile identity attribute %(attribute)s ' 'as it has conflicting values %(new)s and %(old)s') % ( {'attribute': key, 'new': val, 'old': existing_val}) raise exception.Unauthorized(msg) return super(AuthContext, self).__setitem__(key, val)
def validate_v2_token(self, token_ref): """Validate a V2 formatted token. :param token_ref: reference describing the token to validate. Note that token_ref is going to be a token ID. :returns: the token data :raises keystone.exception.TokenNotFound: if token format is invalid :raises keystone.exception.Unauthorized: if v3 token is used """ try: (user_id, methods, audit_ids, domain_id, project_id, trust_id, federated_info, created_at, expires_at) = self.token_formatter.validate_token(token_ref) except exception.ValidationError: raise exception.TokenNotFound(token_id=token_ref) if trust_id or domain_id or federated_info: msg = _('This is not a v2.0 Fernet token. Use v3 for trust, ' 'domain, or federated tokens.') raise exception.Unauthorized(msg) v3_token_data = self.v3_token_data_helper.get_token_data( user_id, methods, project_id=project_id, expires=expires_at, issued_at=created_at, token=token_ref, include_catalog=False, audit_info=audit_ids) token_data = self.v2_token_data_helper.v3_to_v2_token(v3_token_data) token_data['access']['token']['id'] = token_ref return token_data
def _lookup_project(self, project_info): project_id = project_info.get('id') project_name = project_info.get('name') project_ref = None if not project_id and not project_name: raise exception.ValidationError(attribute='id or name', target='project') try: if project_name: if 'domain' not in project_info: raise exception.ValidationError(attribute='domain', target='project') domain_ref = self._lookup_domain(project_info['domain']) project_ref = self.identity_api.get_project_by_name( context=self.context, tenant_name=project_name, domain_id=domain_ref['id']) else: project_ref = self.identity_api.get_project( context=self.context, tenant_id=project_id) except exception.ProjectNotFound as e: LOG.exception(e) raise exception.Unauthorized(e) self._assert_project_is_enabled(project_ref) return project_ref
def _validate_v2_token(self, token_id, belongs_to=None, **kwargs): try: token_ref = self._verify_token(token_id, belongs_to=belongs_to) self._assert_default_domain(token_ref) # FIXME(gyee): performance or correctness? Should we return the # cached token or reconstruct it? Obviously if we are going with # the cached token, any role, project, or domain name changes # will not be reflected. One may argue that with PKI tokens, # we are essentially doing cached token validation anyway. # Lets go with the cached token strategy. Since token # management layer is now pluggable, one can always provide # their own implementation to suit their needs. token_data = token_ref.get('token_data') if (not token_data or self.get_token_version(token_data) != token.provider.V2): # token is created by old v2 logic metadata_ref = token_ref['metadata'] role_refs = [] for role_id in metadata_ref.get('roles', []): role_refs.append(self.identity_api.get_role(role_id)) # Get a service catalog if possible # This is needed for on-behalf-of requests catalog_ref = None if token_ref.get('tenant'): catalog_ref = self.catalog_api.get_catalog( token_ref['user']['id'], token_ref['tenant']['id'], metadata_ref) token_data = self.v2_token_data_helper.get_token_data( token_ref=token_ref, roles_ref=role_refs, catalog_ref=catalog_ref) return token_data except AssertionError as e: LOG.exception(_('Failed to validate token')) raise exception.Unauthorized(e)
def _authenticate_token(self, context, auth): """Try to authenticate using an already existing token. Returns auth_token_data, (user_ref, tenant_ref, metadata_ref) """ if 'token' not in auth: raise exception.ValidationError(attribute='token', target='auth') if "id" not in auth['token']: raise exception.ValidationError(attribute="id", target="token") old_token = auth['token']['id'] try: old_token_ref = self.token_api.get_token(context=context, token_id=old_token) except exception.NotFound as e: raise exception.Unauthorized(e) user_ref = old_token_ref['user'] user_id = user_ref['id'] current_user_ref = self.identity_api.get_user(context=context, user_id=user_id) tenant_id = self._get_tenant_id_from_auth(context, auth) tenant_ref = self._get_tenant_ref(context, user_id, tenant_id) metadata_ref = self._get_metadata_ref(context, user_id, tenant_id) expiry = old_token_ref['expires'] auth_token_data = self._get_auth_token_data(current_user_ref, tenant_ref, metadata_ref, expiry) return auth_token_data, (current_user_ref, tenant_ref, metadata_ref)
def authenticate(self, context, auth_payload, user_context): try: if 'id' not in auth_payload: raise exception.ValidationError(attribute='id', target=METHOD_NAME) token_id = auth_payload['id'] token_ref = self.token_api.get_token(token_id) wsgi.validate_token_bind(context, token_ref) user_context.setdefault( 'user_id', token_ref['token_data']['token']['user']['id']) # to support Grizzly-3 to Grizzly-RC1 transition expires_at = token_ref['token_data']['token'].get( 'expires_at', token_ref['token_data']['token'].get('expires')) user_context.setdefault('expires_at', expires_at) user_context['extras'].update( token_ref['token_data']['token']['extras']) user_context['method_names'].extend( token_ref['token_data']['token']['methods']) if ('OS-TRUST:trust' in token_ref['token_data']['token'] or 'trust' in token_ref['token_data']['token']): raise exception.Forbidden() except AssertionError as e: LOG.error(e) raise exception.Unauthorized(e)
def _get_domain_id_for_request(self, context): """Get the domain_id for a v3 call.""" if context['is_admin']: return DEFAULT_DOMAIN_ID # Fish the domain_id out of the token # # 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 = self.token_api.get_token( token_id=context['token_id']) except exception.TokenNotFound: LOG.warning(_('Invalid token in _get_domain_id_for_request')) raise exception.Unauthorized() if 'domain' in token_ref: return token_ref['domain']['id'] else: return DEFAULT_DOMAIN_ID
def authenticate(self, context, auth_info, auth_context): """Authenticate user.""" # The 'external' method allows any 'REMOTE_USER' based authentication if 'REMOTE_USER' in context['environment']: try: external = get_auth_method('external') external.authenticate(context, auth_info, auth_context) except exception.AuthMethodNotSupported: # This will happen there is no 'external' plugin registered # and the container is performing authentication. # The 'kerberos' and 'saml' methods will be used this way. # In those cases, it is correct to not register an # 'external' plugin; if there is both an 'external' and a # 'kerberos' plugin, it would run the check on identity twice. pass # need to aggregate the results in case two or more methods # are specified auth_response = {'methods': []} for method_name in auth_info.get_method_names(): method = get_auth_method(method_name) resp = method.authenticate(context, auth_info.get_method_data(method_name), auth_context) if resp: auth_response['methods'].append(method_name) auth_response[method_name] = resp if auth_response["methods"]: # authentication continuation required raise exception.AdditionalAuthRequired(auth_response) if 'user_id' not in auth_context: msg = _('User not found') raise exception.Unauthorized(msg)
def _build_auth_context(self, request): token_id = request.headers.get(AUTH_TOKEN_HEADER) 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 = self.token_api.get_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['token_data']) except exception.TokenNotFound: LOG.warning(_('RBAC: Invalid token')) raise exception.Unauthorized()
def authenticate_for_token(self, context, auth=None): """Authenticate user and issue a token.""" include_catalog = 'nocatalog' not in context['query_string'] try: auth_info = AuthInfo.create(context, auth=auth) auth_context = AuthContext(extras={}, method_names=[], bind={}) self.authenticate(context, auth_info, auth_context) if auth_context.get('access_token_id'): auth_info.set_scope(None, auth_context['project_id'], None) self._check_and_set_default_scoping(auth_info, auth_context) (domain_id, project_id, trust) = auth_info.get_scope() method_names = auth_info.get_method_names() method_names += auth_context.get('method_names', []) # make sure the list is unique method_names = list(set(method_names)) expires_at = auth_context.get('expires_at') # NOTE(morganfainberg): define this here so it is clear what the # argument is during the issue_v3_token provider call. metadata_ref = None (token_id, token_data) = self.token_provider_api.issue_v3_token( auth_context['user_id'], method_names, expires_at, project_id, domain_id, auth_context, trust, metadata_ref, include_catalog) # NOTE(wanghong): We consume a trust use only when we are using # trusts and have successfully issued a token. if trust: self.trust_api.consume_use(trust['id']) return render_token_data_response(token_id, token_data, created=True) except exception.TrustNotFound as e: raise exception.Unauthorized(e)