"Fossies" - the Fresh Open Source Software Archive

Member "keystone-16.0.2/keystone/api/users.py" (7 Jun 2021, 35809 Bytes) of package /linux/misc/openstack/keystone-16.0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "users.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_16.0.2.

    1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 #    not use this file except in compliance with the License. You may obtain
    3 #    a copy of the License at
    4 #
    5 #         http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 #    Unless required by applicable law or agreed to in writing, software
    8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 #    License for the specific language governing permissions and limitations
   11 #    under the License.
   12 
   13 # This file handles all flask-restful resources for /v3/users
   14 
   15 import base64
   16 import os
   17 import uuid
   18 
   19 import flask
   20 from oslo_serialization import jsonutils
   21 from six.moves import http_client
   22 from werkzeug import exceptions
   23 
   24 from keystone.api._shared import json_home_relations
   25 from keystone.application_credential import schema as app_cred_schema
   26 from keystone.common import json_home
   27 from keystone.common import provider_api
   28 from keystone.common import rbac_enforcer
   29 from keystone.common import utils
   30 from keystone.common import validation
   31 import keystone.conf
   32 from keystone import exception as ks_exception
   33 from keystone.i18n import _
   34 from keystone.identity import schema
   35 from keystone import notifications
   36 from keystone.server import flask as ks_flask
   37 
   38 
   39 CRED_TYPE_EC2 = 'ec2'
   40 CONF = keystone.conf.CONF
   41 ENFORCER = rbac_enforcer.RBACEnforcer
   42 PROVIDERS = provider_api.ProviderAPIs
   43 
   44 ACCESS_TOKEN_ID_PARAMETER_RELATION = (
   45     json_home_relations.os_oauth1_parameter_rel_func(
   46         parameter_name='access_token_id')
   47 )
   48 
   49 
   50 def _convert_v3_to_ec2_credential(credential):
   51     # Prior to bug #1259584 fix, blob was stored unserialized
   52     # but it should be stored as a json string for compatibility
   53     # with the v3 credentials API.  Fall back to the old behavior
   54     # for backwards compatibility with existing DB contents
   55     try:
   56         blob = jsonutils.loads(credential['blob'])
   57     except TypeError:
   58         blob = credential['blob']
   59     return {'user_id': credential.get('user_id'),
   60             'tenant_id': credential.get('project_id'),
   61             'access': blob.get('access'),
   62             'secret': blob.get('secret'),
   63             'trust_id': blob.get('trust_id')}
   64 
   65 
   66 def _format_token_entity(entity):
   67 
   68     formatted_entity = entity.copy()
   69     access_token_id = formatted_entity['id']
   70     user_id = formatted_entity.get('authorizing_user_id', '')
   71     if 'role_ids' in entity:
   72         formatted_entity.pop('role_ids')
   73     if 'access_secret' in entity:
   74         formatted_entity.pop('access_secret')
   75 
   76     url = ('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(access_token_id)s'
   77            '/roles' % {'user_id': user_id,
   78                        'access_token_id': access_token_id})
   79 
   80     formatted_entity.setdefault('links', {})
   81     formatted_entity['links']['roles'] = (ks_flask.base_url(url))
   82 
   83     return formatted_entity
   84 
   85 
   86 def _check_unrestricted_application_credential(token):
   87     if 'application_credential' in token.methods:
   88         if not token.application_credential['unrestricted']:
   89             action = _("Using method 'application_credential' is not "
   90                        "allowed for managing additional application "
   91                        "credentials.")
   92             raise ks_exception.ForbiddenAction(action=action)
   93 
   94 
   95 def _build_user_target_enforcement():
   96     target = {}
   97     try:
   98         target['user'] = PROVIDERS.identity_api.get_user(
   99             flask.request.view_args.get('user_id')
  100         )
  101         if flask.request.view_args.get('group_id'):
  102             target['group'] = PROVIDERS.identity_api.get_group(
  103                 flask.request.view_args.get('group_id')
  104             )
  105     except ks_exception.NotFound:  # nosec
  106         # Defer existence in the event the user doesn't exist, we'll
  107         # check this later anyway.
  108         pass
  109 
  110     return target
  111 
  112 
  113 def _build_enforcer_target_data_owner_and_user_id_match():
  114     ref = {}
  115     if flask.request.view_args:
  116         credential_id = flask.request.view_args.get('credential_id')
  117         if credential_id is not None:
  118             hashed_id = utils.hash_access_key(credential_id)
  119             ref['credential'] = PROVIDERS.credential_api.get_credential(
  120                 hashed_id)
  121     return ref
  122 
  123 
  124 def _update_request_user_id_attribute():
  125     # This method handles a special case in policy enforcement. The application
  126     # credential API is underneath the user path (e.g.,
  127     # /v3/users/{user_id}/application_credentials/{application_credential_id}).
  128     # The RBAC enforcer thinks the user to evaluate for application credential
  129     # ownership comes from the path, but it should come from the actual
  130     # application credential reference. By ensuring we pull the user ID from
  131     # the application credential, we close a loop hole where users could
  132     # effectively bypass authorization to view or delete any application
  133     # credential in the system, assuming the attacker knows the application
  134     # credential ID of another user. So long as the attacker matches the user
  135     # ID in the request path to the user in the token of the request, they can
  136     # pass the `rule:owner` policy check. This method protects against that by
  137     # ensuring we use the application credential user ID and not something
  138     # determined from the client.
  139     try:
  140         app_cred = (
  141             PROVIDERS.application_credential_api.get_application_credential(
  142                 flask.request.view_args.get('application_credential_id')
  143             )
  144         )
  145         flask.request.view_args['user_id'] = app_cred['user_id']
  146 
  147         # This target isn't really used in the default policy for application
  148         # credentials, but we return it since we're using this method as a hook
  149         # to update the flask request variables, which are used later in the
  150         # keystone RBAC enforcer to populate the policy_dict, which ultimately
  151         # turns into target attributes.
  152         return {'user_id': app_cred['user_id']}
  153     except ks_exception.NotFound:  # nosec
  154         # Defer existance in the event the application credential doesn't
  155         # exist, we'll check this later anyway.
  156         pass
  157 
  158 
  159 def _format_role_entity(role_id):
  160     role = PROVIDERS.role_api.get_role(role_id)
  161     formatted_entity = role.copy()
  162     if 'description' in role:
  163         formatted_entity.pop('description')
  164     if 'enabled' in role:
  165         formatted_entity.pop('enabled')
  166     return formatted_entity
  167 
  168 
  169 class UserResource(ks_flask.ResourceBase):
  170     collection_key = 'users'
  171     member_key = 'user'
  172     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
  173         api='identity_api', method='get_user')
  174 
  175     def get(self, user_id=None):
  176         """Get a user resource or list users.
  177 
  178         GET/HEAD /v3/users
  179         GET/HEAD /v3/users/{user_id}
  180         """
  181         if user_id is not None:
  182             return self._get_user(user_id)
  183         return self._list_users()
  184 
  185     def _get_user(self, user_id):
  186         """Get a user resource.
  187 
  188         GET/HEAD /v3/users/{user_id}
  189         """
  190         ENFORCER.enforce_call(
  191             action='identity:get_user',
  192             build_target=_build_user_target_enforcement
  193         )
  194         ref = PROVIDERS.identity_api.get_user(user_id)
  195         return self.wrap_member(ref)
  196 
  197     def _list_users(self):
  198         """List users.
  199 
  200         GET/HEAD /v3/users
  201         """
  202         filters = ('domain_id', 'enabled', 'idp_id', 'name', 'protocol_id',
  203                    'unique_id', 'password_expires_at')
  204         target = None
  205         if self.oslo_context.domain_id:
  206             target = {'domain_id': self.oslo_context.domain_id}
  207         hints = self.build_driver_hints(filters)
  208         ENFORCER.enforce_call(
  209             action='identity:list_users', filters=filters, target_attr=target
  210         )
  211         domain = self._get_domain_id_for_list_request()
  212         if domain is None and self.oslo_context.domain_id:
  213             domain = self.oslo_context.domain_id
  214         refs = PROVIDERS.identity_api.list_users(
  215             domain_scope=domain, hints=hints)
  216 
  217         # If the user making the request used a domain-scoped token, let's make
  218         # sure we filter out users that are not in that domain. Otherwise, we'd
  219         # be exposing users in other domains. This if statement is needed in
  220         # case _get_domain_id_for_list_request() short-circuits due to
  221         # configuration and protects against information from other domains
  222         # leaking to people who shouldn't see it.
  223         if self.oslo_context.domain_id:
  224             domain_id = self.oslo_context.domain_id
  225             users = [user for user in refs if user['domain_id'] == domain_id]
  226         else:
  227             users = refs
  228 
  229         return self.wrap_collection(users, hints=hints)
  230 
  231     def post(self):
  232         """Create a user.
  233 
  234         POST /v3/users
  235         """
  236         user_data = self.request_body_json.get('user', {})
  237         target = {'user': user_data}
  238         ENFORCER.enforce_call(
  239             action='identity:create_user', target_attr=target
  240         )
  241         validation.lazy_validate(schema.user_create, user_data)
  242         user_data = self._normalize_dict(user_data)
  243         user_data = self._normalize_domain_id(user_data)
  244         ref = PROVIDERS.identity_api.create_user(
  245             user_data,
  246             initiator=self.audit_initiator)
  247         return self.wrap_member(ref), http_client.CREATED
  248 
  249     def patch(self, user_id):
  250         """Update a user.
  251 
  252         PATCH /v3/users/{user_id}
  253         """
  254         ENFORCER.enforce_call(
  255             action='identity:update_user',
  256             build_target=_build_user_target_enforcement
  257         )
  258         PROVIDERS.identity_api.get_user(user_id)
  259         user_data = self.request_body_json.get('user', {})
  260         validation.lazy_validate(schema.user_update, user_data)
  261         self._require_matching_id(user_data)
  262         ref = PROVIDERS.identity_api.update_user(
  263             user_id, user_data, initiator=self.audit_initiator)
  264         return self.wrap_member(ref)
  265 
  266     def delete(self, user_id):
  267         """Delete a user.
  268 
  269         DELETE /v3/users/{user_id}
  270         """
  271         ENFORCER.enforce_call(
  272             action='identity:delete_user',
  273             build_target=_build_user_target_enforcement
  274         )
  275         PROVIDERS.identity_api.delete_user(user_id)
  276         return None, http_client.NO_CONTENT
  277 
  278 
  279 class UserChangePasswordResource(ks_flask.ResourceBase):
  280     @ks_flask.unenforced_api
  281     def get(self, user_id):
  282         # Special case, GET is not allowed.
  283         raise exceptions.MethodNotAllowed(valid_methods=['POST'])
  284 
  285     @ks_flask.unenforced_api
  286     def post(self, user_id):
  287         user_data = self.request_body_json.get('user', {})
  288         validation.lazy_validate(schema.password_change, user_data)
  289 
  290         try:
  291             PROVIDERS.identity_api.change_password(
  292                 user_id=user_id,
  293                 original_password=user_data['original_password'],
  294                 new_password=user_data['password'],
  295                 initiator=self.audit_initiator)
  296         except AssertionError as e:
  297             raise ks_exception.Unauthorized(
  298                 _('Error when changing user password: %s') % e
  299             )
  300         return None, http_client.NO_CONTENT
  301 
  302 
  303 class UserProjectsResource(ks_flask.ResourceBase):
  304     collection_key = 'projects'
  305     member_key = 'project'
  306     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
  307         api='resource_api', method='get_project')
  308 
  309     def get(self, user_id):
  310         filters = ('domain_id', 'enabled', 'name')
  311         ENFORCER.enforce_call(action='identity:list_user_projects',
  312                               filters=filters,
  313                               build_target=_build_user_target_enforcement)
  314         hints = self.build_driver_hints(filters)
  315         refs = PROVIDERS.assignment_api.list_projects_for_user(user_id)
  316         return self.wrap_collection(refs, hints=hints)
  317 
  318 
  319 class UserGroupsResource(ks_flask.ResourceBase):
  320     collection_key = 'groups'
  321     member_key = 'group'
  322     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
  323         api='identity_api', method='get_group')
  324 
  325     def get(self, user_id):
  326         """Get groups for a user.
  327 
  328         GET/HEAD /v3/users/{user_id}/groups
  329         """
  330         filters = ('name',)
  331         hints = self.build_driver_hints(filters)
  332         ENFORCER.enforce_call(action='identity:list_groups_for_user',
  333                               build_target=_build_user_target_enforcement,
  334                               filters=filters)
  335         refs = PROVIDERS.identity_api.list_groups_for_user(user_id=user_id,
  336                                                            hints=hints)
  337         if (self.oslo_context.domain_id):
  338             filtered_refs = []
  339             for ref in refs:
  340                 if ref['domain_id'] == self.oslo_context.domain_id:
  341                     filtered_refs.append(ref)
  342             refs = filtered_refs
  343         return self.wrap_collection(refs, hints=hints)
  344 
  345 
  346 class _UserOSEC2CredBaseResource(ks_flask.ResourceBase):
  347     collection_key = 'credentials'
  348     member_key = 'credential'
  349 
  350     @classmethod
  351     def _add_self_referential_link(cls, ref, collection_name=None):
  352         # NOTE(morgan): This should be refactored to have an EC2 Cred API with
  353         # a sane prefix instead of overloading the "_add_self_referential_link"
  354         # method. This was chosen as it more closely mirrors the pre-flask
  355         # code (for transition).
  356         path = '/users/%(user_id)s/credentials/OS-EC2/%(credential_id)s'
  357 
  358         url = ks_flask.base_url(path) % {
  359             'user_id': ref['user_id'],
  360             'credential_id': ref['access']}
  361         ref.setdefault('links', {})
  362         ref['links']['self'] = url
  363 
  364 
  365 class UserOSEC2CredentialsResourceListCreate(_UserOSEC2CredBaseResource):
  366     def get(self, user_id):
  367         """List EC2 Credentials for user.
  368 
  369         GET/HEAD /v3/users/{user_id}/credentials/OS-EC2
  370         """
  371         ENFORCER.enforce_call(action='identity:ec2_list_credentials')
  372         PROVIDERS.identity_api.get_user(user_id)
  373         credential_refs = PROVIDERS.credential_api.list_credentials_for_user(
  374             user_id, type=CRED_TYPE_EC2)
  375         collection_refs = [
  376             _convert_v3_to_ec2_credential(cred)
  377             for cred in credential_refs
  378         ]
  379         return self.wrap_collection(collection_refs)
  380 
  381     def post(self, user_id):
  382         """Create EC2 Credential for user.
  383 
  384         POST /v3/users/{user_id}/credentials/OS-EC2
  385         """
  386         target = {}
  387         target['credential'] = {'user_id': user_id}
  388         ENFORCER.enforce_call(action='identity:ec2_create_credential',
  389                               target_attr=target)
  390         PROVIDERS.identity_api.get_user(user_id)
  391         tenant_id = self.request_body_json.get('tenant_id')
  392         PROVIDERS.resource_api.get_project(tenant_id)
  393         blob = dict(
  394             access=uuid.uuid4().hex,
  395             secret=uuid.uuid4().hex,
  396             trust_id=self.oslo_context.trust_id
  397         )
  398         credential_id = utils.hash_access_key(blob['access'])
  399         cred_data = dict(
  400             user_id=user_id,
  401             project_id=tenant_id,
  402             blob=jsonutils.dumps(blob),
  403             id=credential_id,
  404             type=CRED_TYPE_EC2
  405         )
  406         PROVIDERS.credential_api.create_credential(credential_id, cred_data)
  407         ref = _convert_v3_to_ec2_credential(cred_data)
  408         return self.wrap_member(ref), http_client.CREATED
  409 
  410 
  411 class UserOSEC2CredentialsResourceGetDelete(_UserOSEC2CredBaseResource):
  412     @staticmethod
  413     def _get_cred_data(credential_id):
  414         cred = PROVIDERS.credential_api.get_credential(credential_id)
  415         if not cred or cred['type'] != CRED_TYPE_EC2:
  416             raise ks_exception.Unauthorized(
  417                 message=_('EC2 access key not found.'))
  418         return _convert_v3_to_ec2_credential(cred)
  419 
  420     def get(self, user_id, credential_id):
  421         """Get a specific EC2 credential.
  422 
  423         GET/HEAD /users/{user_id}/credentials/OS-EC2/{credential_id}
  424         """
  425         func = _build_enforcer_target_data_owner_and_user_id_match
  426         ENFORCER.enforce_call(
  427             action='identity:ec2_get_credential',
  428             build_target=func)
  429         PROVIDERS.identity_api.get_user(user_id)
  430         ec2_cred_id = utils.hash_access_key(credential_id)
  431         cred_data = self._get_cred_data(ec2_cred_id)
  432         return self.wrap_member(cred_data)
  433 
  434     def delete(self, user_id, credential_id):
  435         """Delete a specific EC2 credential.
  436 
  437         DELETE /users/{user_id}/credentials/OS-EC2/{credential_id}
  438         """
  439         func = _build_enforcer_target_data_owner_and_user_id_match
  440         ENFORCER.enforce_call(action='identity:ec2_delete_credential',
  441                               build_target=func)
  442         PROVIDERS.identity_api.get_user(user_id)
  443         ec2_cred_id = utils.hash_access_key(credential_id)
  444         self._get_cred_data(ec2_cred_id)
  445         PROVIDERS.credential_api.delete_credential(ec2_cred_id)
  446         return None, http_client.NO_CONTENT
  447 
  448 
  449 class _OAuth1ResourceBase(ks_flask.ResourceBase):
  450     collection_key = 'access_tokens'
  451     member_key = 'access_token'
  452 
  453     @classmethod
  454     def _add_self_referential_link(cls, ref, collection_name=None):
  455         # NOTE(morgan): This should be refactored to have an OAuth1 API with
  456         # a sane prefix instead of overloading the "_add_self_referential_link"
  457         # method. This was chosen as it more closely mirrors the pre-flask
  458         # code (for transition).
  459         ref.setdefault('links', {})
  460         path = '/users/%(user_id)s/OS-OAUTH1/access_tokens' % {
  461             'user_id': ref.get('authorizing_user_id', '')
  462         }
  463         ref['links']['self'] = ks_flask.base_url(path) + '/' + ref['id']
  464 
  465 
  466 class OAuth1ListAccessTokensResource(_OAuth1ResourceBase):
  467     def get(self, user_id):
  468         """List OAuth1 Access Tokens for user.
  469 
  470         GET /v3/users/{user_id}/OS-OAUTH1/access_tokens
  471         """
  472         ENFORCER.enforce_call(action='identity:list_access_tokens')
  473         if self.oslo_context.is_delegated_auth:
  474             raise ks_exception.Forbidden(
  475                 _('Cannot list request tokens with a token '
  476                   'issued via delegation.'))
  477         refs = PROVIDERS.oauth_api.list_access_tokens(user_id)
  478         formatted_refs = ([_format_token_entity(x) for x in refs])
  479         return self.wrap_collection(formatted_refs)
  480 
  481 
  482 class OAuth1AccessTokenCRUDResource(_OAuth1ResourceBase):
  483     def get(self, user_id, access_token_id):
  484         """Get specific access token.
  485 
  486         GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
  487         """
  488         ENFORCER.enforce_call(action='identity:get_access_token')
  489         access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
  490         if access_token['authorizing_user_id'] != user_id:
  491             raise ks_exception.NotFound()
  492         access_token = _format_token_entity(access_token)
  493         return self.wrap_member(access_token)
  494 
  495     def delete(self, user_id, access_token_id):
  496         """Delete specific access token.
  497 
  498         DELETE /v3/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
  499         """
  500         ENFORCER.enforce_call(
  501             action='identity:ec2_delete_credential',
  502             build_target=_build_enforcer_target_data_owner_and_user_id_match)
  503         access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
  504         reason = (
  505             'Invalidating the token cache because an access token for '
  506             'consumer %(consumer_id)s has been deleted. Authorization for '
  507             'users with OAuth tokens will be recalculated and enforced '
  508             'accordingly the next time they authenticate or validate a '
  509             'token.' % {'consumer_id': access_token['consumer_id']}
  510         )
  511         notifications.invalidate_token_cache_notification(reason)
  512         PROVIDERS.oauth_api.delete_access_token(
  513             user_id, access_token_id, initiator=self.audit_initiator)
  514         return None, http_client.NO_CONTENT
  515 
  516 
  517 class OAuth1AccessTokenRoleListResource(ks_flask.ResourceBase):
  518     collection_key = 'roles'
  519     member_key = 'role'
  520 
  521     def get(self, user_id, access_token_id):
  522         """List roles for a user access token.
  523 
  524         GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/
  525                  {access_token_id}/roles
  526         """
  527         ENFORCER.enforce_call(action='identity:list_access_token_roles')
  528         access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
  529         if access_token['authorizing_user_id'] != user_id:
  530             raise ks_exception.NotFound()
  531         authed_role_ids = access_token['role_ids']
  532         authed_role_ids = jsonutils.loads(authed_role_ids)
  533         refs = ([_format_role_entity(x) for x in authed_role_ids])
  534         return self.wrap_collection(refs)
  535 
  536 
  537 class OAuth1AccessTokenRoleResource(ks_flask.ResourceBase):
  538     collection_key = 'roles'
  539     member_key = 'role'
  540 
  541     def get(self, user_id, access_token_id, role_id):
  542         """Get role for access token.
  543 
  544         GET/HEAD /v3/users/{user_id}/OS-OAUTH1/access_tokens/
  545                  {access_token_id}/roles/{role_id}
  546         """
  547         ENFORCER.enforce_call(action='identity:get_access_token_role')
  548         access_token = PROVIDERS.oauth_api.get_access_token(access_token_id)
  549         if access_token['authorizing_user_id'] != user_id:
  550             raise ks_exception.Unauthorized(_('User IDs do not match'))
  551         authed_role_ids = access_token['role_ids']
  552         authed_role_ids = jsonutils.loads(authed_role_ids)
  553         for authed_role_id in authed_role_ids:
  554             if authed_role_id == role_id:
  555                 role = _format_role_entity(role_id)
  556                 return self.wrap_member(role)
  557         raise ks_exception.RoleNotFound(role_id=role_id)
  558 
  559 
  560 class UserAppCredListCreateResource(ks_flask.ResourceBase):
  561     collection_key = 'application_credentials'
  562     member_key = 'application_credential'
  563     _public_parameters = frozenset([
  564         'id',
  565         'name',
  566         'description',
  567         'expires_at',
  568         'project_id',
  569         'roles',
  570         # secret is only exposed after create, it is not stored
  571         'secret',
  572         'links',
  573         'unrestricted',
  574         'access_rules'
  575     ])
  576 
  577     @staticmethod
  578     def _generate_secret():
  579         length = 64
  580         secret = os.urandom(length)
  581         secret = base64.urlsafe_b64encode(secret)
  582         secret = secret.rstrip(b'=')
  583         secret = secret.decode('utf-8')
  584         return secret
  585 
  586     @staticmethod
  587     def _normalize_role_list(app_cred_roles):
  588         roles = []
  589         for role in app_cred_roles:
  590             if role.get('id'):
  591                 roles.append(role)
  592             else:
  593                 roles.append(PROVIDERS.role_api.get_unique_role_by_name(
  594                     role['name']))
  595         return roles
  596 
  597     def _get_roles(self, app_cred_data, token):
  598         if app_cred_data.get('roles'):
  599             roles = self._normalize_role_list(app_cred_data['roles'])
  600             # NOTE(cmurphy): The user is not allowed to add a role that is not
  601             # in their token. This is to prevent trustees or application
  602             # credential users from escallating their privileges to include
  603             # additional roles that the trustor or application credential
  604             # creator has assigned on the project.
  605             token_roles = [r['id'] for r in token.roles]
  606             for role in roles:
  607                 if role['id'] not in token_roles:
  608                     detail = _('Cannot create an application credential with '
  609                                'unassigned role')
  610                     raise ks_exception.ApplicationCredentialValidationError(
  611                         detail=detail)
  612         else:
  613             roles = token.roles
  614         return roles
  615 
  616     def get(self, user_id):
  617         """List application credentials for user.
  618 
  619         GET/HEAD /v3/users/{user_id}/application_credentials
  620         """
  621         filters = ('name',)
  622         ENFORCER.enforce_call(action='identity:list_application_credentials',
  623                               filters=filters)
  624         app_cred_api = PROVIDERS.application_credential_api
  625         hints = self.build_driver_hints(filters)
  626         refs = app_cred_api.list_application_credentials(user_id, hints=hints)
  627         return self.wrap_collection(refs, hints=hints)
  628 
  629     def post(self, user_id):
  630         """Create application credential.
  631 
  632         POST /v3/users/{user_id}/application_credentials
  633         """
  634         ENFORCER.enforce_call(action='identity:create_application_credential')
  635         app_cred_data = self.request_body_json.get(
  636             'application_credential', {})
  637         validation.lazy_validate(app_cred_schema.application_credential_create,
  638                                  app_cred_data)
  639         token = self.auth_context['token']
  640         _check_unrestricted_application_credential(token)
  641         if self.oslo_context.user_id != user_id:
  642             action = _('Cannot create an application credential for another '
  643                        'user.')
  644             raise ks_exception.ForbiddenAction(action=action)
  645         project_id = self.oslo_context.project_id
  646         app_cred_data = self._assign_unique_id(app_cred_data)
  647         if not app_cred_data.get('secret'):
  648             app_cred_data['secret'] = self._generate_secret()
  649         app_cred_data['user_id'] = user_id
  650         app_cred_data['project_id'] = project_id
  651         app_cred_data['roles'] = self._get_roles(app_cred_data, token)
  652         if app_cred_data.get('expires_at'):
  653             app_cred_data['expires_at'] = utils.parse_expiration_date(
  654                 app_cred_data['expires_at'])
  655         if app_cred_data.get('access_rules'):
  656             for access_rule in app_cred_data['access_rules']:
  657                 # If user provides an access rule by ID, it will be looked up
  658                 # by ID. If user provides an access rule that is identical to
  659                 # an existing one, the ID generated here will be ignored and
  660                 # the pre-existing access rule will be used.
  661                 if 'id' not in access_rule:
  662                     # Generate directly, rather than using _assign_unique_id,
  663                     # so that there is no deep copy made
  664                     access_rule['id'] = uuid.uuid4().hex
  665         app_cred_data = self._normalize_dict(app_cred_data)
  666         app_cred_api = PROVIDERS.application_credential_api
  667 
  668         try:
  669             ref = app_cred_api.create_application_credential(
  670                 app_cred_data, initiator=self.audit_initiator)
  671         except ks_exception.RoleAssignmentNotFound as e:
  672             # Raise a Bad Request, not a Not Found, in accordance with the
  673             # API-SIG recommendations:
  674             # https://specs.openstack.org/openstack/api-wg/guidelines/http.html#failure-code-clarifications
  675             raise ks_exception.ApplicationCredentialValidationError(
  676                 detail=str(e))
  677         return self.wrap_member(ref), http_client.CREATED
  678 
  679 
  680 class UserAppCredGetDeleteResource(ks_flask.ResourceBase):
  681     collection_key = 'application_credentials'
  682     member_key = 'application_credential'
  683 
  684     def get(self, user_id, application_credential_id):
  685         """Get application credential resource.
  686 
  687         GET/HEAD /v3/users/{user_id}/application_credentials/
  688                  {application_credential_id}
  689         """
  690         target = _update_request_user_id_attribute()
  691         ENFORCER.enforce_call(
  692             action='identity:get_application_credential',
  693             target_attr=target,
  694         )
  695         ref = PROVIDERS.application_credential_api.get_application_credential(
  696             application_credential_id)
  697         return self.wrap_member(ref)
  698 
  699     def delete(self, user_id, application_credential_id):
  700         """Delete application credential resource.
  701 
  702         DELETE /v3/users/{user_id}/application_credentials/
  703                {application_credential_id}
  704         """
  705         target = _update_request_user_id_attribute()
  706         ENFORCER.enforce_call(
  707             action='identity:delete_application_credential',
  708             target_attr=target
  709         )
  710         token = self.auth_context['token']
  711         _check_unrestricted_application_credential(token)
  712         PROVIDERS.application_credential_api.delete_application_credential(
  713             application_credential_id, initiator=self.audit_initiator)
  714         return None, http_client.NO_CONTENT
  715 
  716 
  717 class UserAccessRuleListResource(ks_flask.ResourceBase):
  718     collection_key = 'access_rules'
  719     member_key = 'access_rule'
  720 
  721     def get(self, user_id):
  722         """List access rules for user.
  723 
  724         GET/HEAD /v3/users/{user_id}/access_rules
  725         """
  726         filters = ('service', 'path', 'method',)
  727         ENFORCER.enforce_call(action='identity:list_access_rules',
  728                               filters=filters,
  729                               build_target=_build_user_target_enforcement)
  730         app_cred_api = PROVIDERS.application_credential_api
  731         hints = self.build_driver_hints(filters)
  732         refs = app_cred_api.list_access_rules_for_user(user_id, hints=hints)
  733         hints = self.build_driver_hints(filters)
  734         return self.wrap_collection(refs, hints=hints)
  735 
  736 
  737 class UserAccessRuleGetDeleteResource(ks_flask.ResourceBase):
  738     collection_key = 'access_rules'
  739     member_key = 'access_rule'
  740 
  741     def get(self, user_id, access_rule_id):
  742         """Get access rule resource.
  743 
  744         GET/HEAD /v3/users/{user_id}/access_rules/{access_rule_id}
  745         """
  746         ENFORCER.enforce_call(
  747             action='identity:get_access_rule',
  748             build_target=_build_user_target_enforcement
  749         )
  750         ref = PROVIDERS.application_credential_api.get_access_rule(
  751             access_rule_id)
  752         return self.wrap_member(ref)
  753 
  754     def delete(self, user_id, access_rule_id):
  755         """Delete access rule resource.
  756 
  757         DELETE /v3/users/{user_id}/access_rules/{access_rule_id}
  758         """
  759         ENFORCER.enforce_call(
  760             action='identity:delete_access_rule',
  761             build_target=_build_user_target_enforcement
  762         )
  763         PROVIDERS.application_credential_api.delete_access_rule(
  764             access_rule_id, initiator=self.audit_initiator)
  765         return None, http_client.NO_CONTENT
  766 
  767 
  768 class UserAPI(ks_flask.APIBase):
  769     _name = 'users'
  770     _import_name = __name__
  771     resources = [UserResource]
  772     resource_mapping = [
  773         ks_flask.construct_resource_map(
  774             resource=UserChangePasswordResource,
  775             url='/users/<string:user_id>/password',
  776             resource_kwargs={},
  777             rel='user_change_password',
  778             path_vars={'user_id': json_home.Parameters.USER_ID}
  779         ),
  780         ks_flask.construct_resource_map(
  781             resource=UserGroupsResource,
  782             url='/users/<string:user_id>/groups',
  783             resource_kwargs={},
  784             rel='user_groups',
  785             path_vars={'user_id': json_home.Parameters.USER_ID}
  786         ),
  787         ks_flask.construct_resource_map(
  788             resource=UserProjectsResource,
  789             url='/users/<string:user_id>/projects',
  790             resource_kwargs={},
  791             rel='user_projects',
  792             path_vars={'user_id': json_home.Parameters.USER_ID}
  793         ),
  794         ks_flask.construct_resource_map(
  795             resource=UserOSEC2CredentialsResourceListCreate,
  796             url='/users/<string:user_id>/credentials/OS-EC2',
  797             resource_kwargs={},
  798             rel='user_credentials',
  799             resource_relation_func=(
  800                 json_home_relations.os_ec2_resource_rel_func),
  801             path_vars={'user_id': json_home.Parameters.USER_ID}
  802         ),
  803         ks_flask.construct_resource_map(
  804             resource=UserOSEC2CredentialsResourceGetDelete,
  805             url=('/users/<string:user_id>/credentials/OS-EC2/'
  806                  '<string:credential_id>'),
  807             resource_kwargs={},
  808             rel='user_credential',
  809             resource_relation_func=(
  810                 json_home_relations.os_ec2_resource_rel_func),
  811             path_vars={
  812                 'credential_id': json_home.build_v3_parameter_relation(
  813                     'credential_id'),
  814                 'user_id': json_home.Parameters.USER_ID}
  815         ),
  816         ks_flask.construct_resource_map(
  817             resource=OAuth1ListAccessTokensResource,
  818             url='/users/<string:user_id>/OS-OAUTH1/access_tokens',
  819             resource_kwargs={},
  820             rel='user_access_tokens',
  821             resource_relation_func=(
  822                 json_home_relations.os_oauth1_resource_rel_func),
  823             path_vars={'user_id': json_home.Parameters.USER_ID}
  824         ),
  825         ks_flask.construct_resource_map(
  826             resource=OAuth1AccessTokenCRUDResource,
  827             url=('/users/<string:user_id>/OS-OAUTH1/'
  828                  'access_tokens/<string:access_token_id>'),
  829             resource_kwargs={},
  830             rel='user_access_token',
  831             resource_relation_func=(
  832                 json_home_relations.os_oauth1_resource_rel_func),
  833             path_vars={
  834                 'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
  835                 'user_id': json_home.Parameters.USER_ID}
  836         ),
  837         ks_flask.construct_resource_map(
  838             resource=OAuth1AccessTokenRoleListResource,
  839             url=('/users/<string:user_id>/OS-OAUTH1/access_tokens/'
  840                  '<string:access_token_id>/roles'),
  841             resource_kwargs={},
  842             rel='user_access_token_roles',
  843             resource_relation_func=(
  844                 json_home_relations.os_oauth1_resource_rel_func),
  845             path_vars={'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
  846                        'user_id': json_home.Parameters.USER_ID}
  847         ),
  848         ks_flask.construct_resource_map(
  849             resource=OAuth1AccessTokenRoleResource,
  850             url=('/users/<string:user_id>/OS-OAUTH1/access_tokens/'
  851                  '<string:access_token_id>/roles/<string:role_id>'),
  852             resource_kwargs={},
  853             rel='user_access_token_role',
  854             resource_relation_func=(
  855                 json_home_relations.os_oauth1_resource_rel_func),
  856             path_vars={'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
  857                        'role_id': json_home.Parameters.ROLE_ID,
  858                        'user_id': json_home.Parameters.USER_ID}
  859         ),
  860         ks_flask.construct_resource_map(
  861             resource=UserAppCredListCreateResource,
  862             url='/users/<string:user_id>/application_credentials',
  863             resource_kwargs={},
  864             rel='application_credentials',
  865             path_vars={'user_id': json_home.Parameters.USER_ID}
  866         ),
  867         ks_flask.construct_resource_map(
  868             resource=UserAppCredGetDeleteResource,
  869             url=('/users/<string:user_id>/application_credentials/'
  870                  '<string:application_credential_id>'),
  871             resource_kwargs={},
  872             rel='application_credential',
  873             path_vars={
  874                 'user_id': json_home.Parameters.USER_ID,
  875                 'application_credential_id':
  876                     json_home.Parameters.APPLICATION_CRED_ID}
  877         ),
  878         ks_flask.construct_resource_map(
  879             resource=UserAccessRuleListResource,
  880             url='/users/<string:user_id>/access_rules',
  881             resource_kwargs={},
  882             rel='access_rules',
  883             path_vars={'user_id': json_home.Parameters.USER_ID}
  884         ),
  885         ks_flask.construct_resource_map(
  886             resource=UserAccessRuleGetDeleteResource,
  887             url=('/users/<string:user_id>/access_rules/'
  888                  '<string:access_rule_id>'),
  889             resource_kwargs={},
  890             rel='access_rule',
  891             path_vars={
  892                 'user_id': json_home.Parameters.USER_ID,
  893                 'access_rule_id':
  894                     json_home.Parameters.ACCESS_RULE_ID}
  895         )
  896     ]
  897 
  898 
  899 APIs = (UserAPI,)