"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/api/projects.py" (13 May 2020, 21741 Bytes) of package /linux/misc/openstack/keystone-17.0.0.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 "projects.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    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/projects
   14 
   15 import functools
   16 
   17 import flask
   18 import http.client
   19 
   20 from keystone.common import json_home
   21 from keystone.common import provider_api
   22 from keystone.common import rbac_enforcer
   23 from keystone.common import validation
   24 import keystone.conf
   25 from keystone import exception
   26 from keystone.i18n import _
   27 from keystone.resource import schema
   28 from keystone.server import flask as ks_flask
   29 
   30 CONF = keystone.conf.CONF
   31 ENFORCER = rbac_enforcer.RBACEnforcer
   32 PROVIDERS = provider_api.ProviderAPIs
   33 
   34 
   35 def _build_project_target_enforcement():
   36     target = {}
   37     try:
   38         target['project'] = PROVIDERS.resource_api.get_project(
   39             flask.request.view_args.get('project_id')
   40         )
   41     except exception.NotFound:  # nosec
   42         # Defer existence in the event the project doesn't exist, we'll
   43         # check this later anyway.
   44         pass
   45 
   46     return target
   47 
   48 
   49 class ProjectResource(ks_flask.ResourceBase):
   50     collection_key = 'projects'
   51     member_key = 'project'
   52     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
   53         api='resource_api', method='get_project')
   54 
   55     def _expand_project_ref(self, ref):
   56         parents_as_list = self.query_filter_is_true('parents_as_list')
   57         parents_as_ids = self.query_filter_is_true('parents_as_ids')
   58 
   59         subtree_as_list = self.query_filter_is_true('subtree_as_list')
   60 
   61         subtree_as_ids = self.query_filter_is_true('subtree_as_ids')
   62         include_limits = self.query_filter_is_true('include_limits')
   63 
   64         # parents_as_list and parents_as_ids are mutually exclusive
   65         if parents_as_list and parents_as_ids:
   66             msg = _('Cannot use parents_as_list and parents_as_ids query '
   67                     'params at the same time.')
   68             raise exception.ValidationError(msg)
   69 
   70         # subtree_as_list and subtree_as_ids are mutually exclusive
   71         if subtree_as_list and subtree_as_ids:
   72             msg = _('Cannot use subtree_as_list and subtree_as_ids query '
   73                     'params at the same time.')
   74             raise exception.ValidationError(msg)
   75 
   76         if parents_as_list:
   77             parents = PROVIDERS.resource_api.list_project_parents(
   78                 ref['id'], self.oslo_context.user_id, include_limits)
   79             ref['parents'] = [self.wrap_member(p)
   80                               for p in parents]
   81         elif parents_as_ids:
   82             ref['parents'] = PROVIDERS.resource_api.get_project_parents_as_ids(
   83                 ref
   84             )
   85 
   86         if subtree_as_list:
   87             subtree = PROVIDERS.resource_api.list_projects_in_subtree(
   88                 ref['id'], self.oslo_context.user_id, include_limits)
   89             ref['subtree'] = [self.wrap_member(p)
   90                               for p in subtree]
   91         elif subtree_as_ids:
   92             ref['subtree'] = (
   93                 PROVIDERS.resource_api.get_projects_in_subtree_as_ids(
   94                     ref['id']
   95                 )
   96             )
   97 
   98     def _get_project(self, project_id):
   99         """Get project.
  100 
  101         GET/HEAD /v3/projects/{project_id}
  102         """
  103         ENFORCER.enforce_call(
  104             action='identity:get_project',
  105             build_target=_build_project_target_enforcement
  106         )
  107         project = PROVIDERS.resource_api.get_project(project_id)
  108         self._expand_project_ref(project)
  109         return self.wrap_member(project)
  110 
  111     def _list_projects(self):
  112         """List projects.
  113 
  114         GET/HEAD /v3/projects
  115         """
  116         filters = ('domain_id', 'enabled', 'name', 'parent_id', 'is_domain')
  117         target = None
  118         if self.oslo_context.domain_id:
  119             target = {'domain_id': self.oslo_context.domain_id}
  120         ENFORCER.enforce_call(action='identity:list_projects',
  121                               filters=filters,
  122                               target_attr=target)
  123         hints = self.build_driver_hints(filters)
  124 
  125         # If 'is_domain' has not been included as a query, we default it to
  126         # False (which in query terms means '0')
  127         if 'is_domain' not in flask.request.args:
  128             hints.add_filter('is_domain', '0')
  129 
  130         tag_params = ['tags', 'tags-any', 'not-tags', 'not-tags-any']
  131         for t in tag_params:
  132             if t in flask.request.args:
  133                 hints.add_filter(t, flask.request.args[t])
  134         refs = PROVIDERS.resource_api.list_projects(hints=hints)
  135         if self.oslo_context.domain_id:
  136             domain_id = self.oslo_context.domain_id
  137             filtered_refs = [
  138                 ref for ref in refs if ref['domain_id'] == domain_id
  139             ]
  140         else:
  141             filtered_refs = refs
  142         return self.wrap_collection(filtered_refs, hints=hints)
  143 
  144     def get(self, project_id=None):
  145         """Get project or list projects.
  146 
  147         GET/HEAD /v3/projects
  148         GET/HEAD /v3/projects/{project_id}
  149         """
  150         if project_id is not None:
  151             return self._get_project(project_id)
  152         else:
  153             return self._list_projects()
  154 
  155     def post(self):
  156         """Create project.
  157 
  158         POST /v3/projects
  159         """
  160         project = self.request_body_json.get('project', {})
  161         target = {'project': project}
  162         ENFORCER.enforce_call(
  163             action='identity:create_project', target_attr=target
  164         )
  165         validation.lazy_validate(schema.project_create, project)
  166         project = self._assign_unique_id(project)
  167         if not project.get('is_domain'):
  168             project = self._normalize_domain_id(project)
  169             # Our API requires that you specify the location in the hierarchy
  170             # unambiguously. This could be by parent_id or, if it is a top
  171             # level project, just by providing a domain_id.
  172         if not project.get('parent_id'):
  173             project['parent_id'] = project.get('domain_id')
  174         project = self._normalize_dict(project)
  175         try:
  176             ref = PROVIDERS.resource_api.create_project(
  177                 project['id'],
  178                 project,
  179                 initiator=self.audit_initiator)
  180         except (exception.DomainNotFound, exception.ProjectNotFound) as e:
  181             raise exception.ValidationError(e)
  182         return self.wrap_member(ref), http.client.CREATED
  183 
  184     def patch(self, project_id):
  185         """Update project.
  186 
  187         PATCH /v3/projects/{project_id}
  188         """
  189         ENFORCER.enforce_call(
  190             action='identity:update_project',
  191             build_target=_build_project_target_enforcement
  192         )
  193         project = self.request_body_json.get('project', {})
  194         validation.lazy_validate(schema.project_update, project)
  195         self._require_matching_id(project)
  196         ref = PROVIDERS.resource_api.update_project(
  197             project_id,
  198             project,
  199             initiator=self.audit_initiator)
  200         return self.wrap_member(ref)
  201 
  202     def delete(self, project_id):
  203         """Delete project.
  204 
  205         DELETE /v3/projects/{project_id}
  206         """
  207         ENFORCER.enforce_call(
  208             action='identity:delete_project',
  209             build_target=_build_project_target_enforcement
  210         )
  211         PROVIDERS.resource_api.delete_project(
  212             project_id,
  213             initiator=self.audit_initiator)
  214         return None, http.client.NO_CONTENT
  215 
  216 
  217 class _ProjectTagResourceBase(ks_flask.ResourceBase):
  218     collection_key = 'projects'
  219     member_key = 'tags'
  220     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
  221         api='resource_api', method='get_project_tag')
  222 
  223     @classmethod
  224     def wrap_member(cls, ref, collection_name=None, member_name=None):
  225         member_name = member_name or cls.member_key
  226         # NOTE(gagehugo): Overriding this due to how the common controller
  227         # expects the ref to have an id, which for tags it does not.
  228         new_ref = {'links': {'self': ks_flask.full_url()}}
  229         new_ref[member_name] = (ref or [])
  230         return new_ref
  231 
  232 
  233 class ProjectTagsResource(_ProjectTagResourceBase):
  234     def get(self, project_id):
  235         """List tags associated with a given project.
  236 
  237         GET /v3/projects/{project_id}/tags
  238         """
  239         ENFORCER.enforce_call(
  240             action='identity:list_project_tags',
  241             build_target=_build_project_target_enforcement
  242         )
  243         ref = PROVIDERS.resource_api.list_project_tags(project_id)
  244         return self.wrap_member(ref)
  245 
  246     def put(self, project_id):
  247         """Update all tags associated with a given project.
  248 
  249         PUT /v3/projects/{project_id}/tags
  250         """
  251         ENFORCER.enforce_call(
  252             action='identity:update_project_tags',
  253             build_target=_build_project_target_enforcement
  254         )
  255         tags = self.request_body_json.get('tags', {})
  256         validation.lazy_validate(schema.project_tags_update, tags)
  257         ref = PROVIDERS.resource_api.update_project_tags(
  258             project_id, tags, initiator=self.audit_initiator)
  259         return self.wrap_member(ref)
  260 
  261     def delete(self, project_id):
  262         """Delete all tags associated with a given project.
  263 
  264         DELETE /v3/projects/{project_id}/tags
  265         """
  266         ENFORCER.enforce_call(
  267             action='identity:delete_project_tags',
  268             build_target=_build_project_target_enforcement
  269         )
  270         PROVIDERS.resource_api.update_project_tags(project_id, [])
  271         return None, http.client.NO_CONTENT
  272 
  273 
  274 class ProjectTagResource(_ProjectTagResourceBase):
  275     def get(self, project_id, value):
  276         """Get information for a single tag associated with a given project.
  277 
  278         GET /v3/projects/{project_id}/tags/{value}
  279         """
  280         ENFORCER.enforce_call(
  281             action='identity:get_project_tag',
  282             build_target=_build_project_target_enforcement,
  283         )
  284         PROVIDERS.resource_api.get_project_tag(project_id, value)
  285         return None, http.client.NO_CONTENT
  286 
  287     def put(self, project_id, value):
  288         """Add a single tag to a project.
  289 
  290         PUT /v3/projects/{project_id}/tags/{value}
  291         """
  292         ENFORCER.enforce_call(
  293             action='identity:create_project_tag',
  294             build_target=_build_project_target_enforcement
  295         )
  296         validation.lazy_validate(schema.project_tag_create, value)
  297         # Check if we will exceed the max number of tags on this project
  298         tags = PROVIDERS.resource_api.list_project_tags(project_id)
  299         tags.append(value)
  300         validation.lazy_validate(schema.project_tags_update, tags)
  301         PROVIDERS.resource_api.create_project_tag(
  302             project_id,
  303             value,
  304             initiator=self.audit_initiator
  305         )
  306         url = '/'.join((ks_flask.base_url(), project_id, 'tags', value))
  307         response = flask.make_response('', http.client.CREATED)
  308         response.headers['Location'] = url
  309         return response
  310 
  311     def delete(self, project_id, value):
  312         """Delete a single tag from a project.
  313 
  314         /v3/projects/{project_id}/tags/{value}
  315         """
  316         ENFORCER.enforce_call(
  317             action='identity:delete_project_tag',
  318             build_target=_build_project_target_enforcement
  319         )
  320         PROVIDERS.resource_api.delete_project_tag(project_id, value)
  321         return None, http.client.NO_CONTENT
  322 
  323 
  324 class _ProjectGrantResourceBase(ks_flask.ResourceBase):
  325     collection_key = 'roles'
  326     member_key = 'role'
  327     get_member_from_driver = PROVIDERS.deferred_provider_lookup(
  328         api='role_api', method='get_role')
  329 
  330     @staticmethod
  331     def _check_if_inherited():
  332         return flask.request.path.endswith('/inherited_to_projects')
  333 
  334     @staticmethod
  335     def _build_enforcement_target_attr(role_id=None, user_id=None,
  336                                        group_id=None, domain_id=None,
  337                                        project_id=None,
  338                                        allow_non_existing=False):
  339         ref = {}
  340         if role_id:
  341             ref['role'] = PROVIDERS.role_api.get_role(role_id)
  342 
  343         try:
  344             if user_id:
  345                 ref['user'] = PROVIDERS.identity_api.get_user(user_id)
  346             else:
  347                 ref['group'] = PROVIDERS.identity_api.get_group(group_id)
  348         except (exception.UserNotFound, exception.GroupNotFound):
  349             if not allow_non_existing:
  350                 raise
  351 
  352         # NOTE(lbragstad): This if/else check will need to be expanded in the
  353         # future to handle system hierarchies if that is implemented.
  354         if domain_id:
  355             ref['domain'] = PROVIDERS.resource_api.get_domain(domain_id)
  356         elif project_id:
  357             ref['project'] = PROVIDERS.resource_api.get_project(project_id)
  358 
  359         return ref
  360 
  361 
  362 class ProjectUserGrantResource(_ProjectGrantResourceBase):
  363     def get(self, project_id, user_id, role_id):
  364         """Check grant for project, user, role.
  365 
  366         GET/HEAD /v3/projects/{project_id/users/{user_id}/roles/{role_id}
  367         """
  368         ENFORCER.enforce_call(
  369             action='identity:check_grant',
  370             build_target=functools.partial(
  371                 self._build_enforcement_target_attr, role_id=role_id,
  372                 project_id=project_id, user_id=user_id)
  373         )
  374         inherited = self._check_if_inherited()
  375         PROVIDERS.assignment_api.get_grant(
  376             role_id=role_id, user_id=user_id, project_id=project_id,
  377             inherited_to_projects=inherited)
  378         return None, http.client.NO_CONTENT
  379 
  380     def put(self, project_id, user_id, role_id):
  381         """Grant role for user on project.
  382 
  383         PUT /v3/projects/{project_id}/users/{user_id}/roles/{role_id}
  384         """
  385         ENFORCER.enforce_call(
  386             action='identity:create_grant',
  387             build_target=functools.partial(
  388                 self._build_enforcement_target_attr,
  389                 role_id=role_id, project_id=project_id, user_id=user_id)
  390         )
  391         inherited = self._check_if_inherited()
  392         PROVIDERS.assignment_api.create_grant(
  393             role_id=role_id, user_id=user_id, project_id=project_id,
  394             inherited_to_projects=inherited, initiator=self.audit_initiator)
  395         return None, http.client.NO_CONTENT
  396 
  397     def delete(self, project_id, user_id, role_id):
  398         """Delete grant of role for user on project.
  399 
  400         DELETE /v3/projects/{project_id}/users/{user_id}/roles/{role_id}
  401         """
  402         ENFORCER.enforce_call(
  403             action='identity:revoke_grant',
  404             build_target=functools.partial(
  405                 self._build_enforcement_target_attr,
  406                 role_id=role_id, user_id=user_id, project_id=project_id,
  407                 allow_non_existing=True)
  408         )
  409         inherited = self._check_if_inherited()
  410         PROVIDERS.assignment_api.delete_grant(
  411             role_id=role_id, user_id=user_id, project_id=project_id,
  412             inherited_to_projects=inherited, initiator=self.audit_initiator)
  413         return None, http.client.NO_CONTENT
  414 
  415 
  416 class ProjectUserListGrantResource(_ProjectGrantResourceBase):
  417     def get(self, project_id, user_id):
  418         """List grants for user on project.
  419 
  420         GET/HEAD /v3/projects/{project_id}/users/{user_id}
  421         """
  422         ENFORCER.enforce_call(
  423             action='identity:list_grants',
  424             build_target=functools.partial(
  425                 self._build_enforcement_target_attr,
  426                 project_id=project_id, user_id=user_id)
  427         )
  428         inherited = self._check_if_inherited()
  429         refs = PROVIDERS.assignment_api.list_grants(
  430             user_id=user_id, project_id=project_id,
  431             inherited_to_projects=inherited)
  432         return self.wrap_collection(refs)
  433 
  434 
  435 class ProjectGroupGrantResource(_ProjectGrantResourceBase):
  436     def get(self, project_id, group_id, role_id):
  437         """Check grant for project, group, role.
  438 
  439         GET/HEAD /v3/projects/{project_id/groups/{group_id}/roles/{role_id}
  440         """
  441         ENFORCER.enforce_call(
  442             action='identity:check_grant',
  443             build_target=functools.partial(
  444                 self._build_enforcement_target_attr, role_id=role_id,
  445                 project_id=project_id, group_id=group_id)
  446         )
  447         inherited = self._check_if_inherited()
  448         PROVIDERS.assignment_api.get_grant(
  449             role_id=role_id, group_id=group_id, project_id=project_id,
  450             inherited_to_projects=inherited)
  451         return None, http.client.NO_CONTENT
  452 
  453     def put(self, project_id, group_id, role_id):
  454         """Grant role for group on project.
  455 
  456         PUT /v3/projects/{project_id}/groups/{group_id}/roles/{role_id}
  457         """
  458         ENFORCER.enforce_call(
  459             action='identity:create_grant',
  460             build_target=functools.partial(
  461                 self._build_enforcement_target_attr,
  462                 role_id=role_id, project_id=project_id, group_id=group_id)
  463         )
  464         inherited = self._check_if_inherited()
  465         PROVIDERS.assignment_api.create_grant(
  466             role_id=role_id, group_id=group_id, project_id=project_id,
  467             inherited_to_projects=inherited, initiator=self.audit_initiator)
  468         return None, http.client.NO_CONTENT
  469 
  470     def delete(self, project_id, group_id, role_id):
  471         """Delete grant of role for group on project.
  472 
  473         DELETE /v3/projects/{project_id}/groups/{group_id}/roles/{role_id}
  474         """
  475         ENFORCER.enforce_call(
  476             action='identity:revoke_grant',
  477             build_target=functools.partial(
  478                 self._build_enforcement_target_attr,
  479                 role_id=role_id, group_id=group_id, project_id=project_id,
  480                 allow_non_existing=True)
  481         )
  482         inherited = self._check_if_inherited()
  483         PROVIDERS.assignment_api.delete_grant(
  484             role_id=role_id, group_id=group_id, project_id=project_id,
  485             inherited_to_projects=inherited, initiator=self.audit_initiator)
  486         return None, http.client.NO_CONTENT
  487 
  488 
  489 class ProjectGroupListGrantResource(_ProjectGrantResourceBase):
  490     def get(self, project_id, group_id):
  491         """List grants for group on project.
  492 
  493         GET/HEAD /v3/projects/{project_id}/groups/{group_id}
  494         """
  495         ENFORCER.enforce_call(
  496             action='identity:list_grants',
  497             build_target=functools.partial(
  498                 self._build_enforcement_target_attr,
  499                 project_id=project_id, group_id=group_id)
  500         )
  501         inherited = self._check_if_inherited()
  502         refs = PROVIDERS.assignment_api.list_grants(
  503             group_id=group_id, project_id=project_id,
  504             inherited_to_projects=inherited)
  505         return self.wrap_collection(refs)
  506 
  507 
  508 class ProjectAPI(ks_flask.APIBase):
  509     _name = 'projects'
  510     _import_name = __name__
  511     resources = [ProjectResource]
  512     resource_mapping = [
  513         ks_flask.construct_resource_map(
  514             resource=ProjectTagsResource,
  515             url='/projects/<string:project_id>/tags',
  516             resource_kwargs={},
  517             rel='project_tags',
  518             path_vars={
  519                 'project_id': json_home.Parameters.PROJECT_ID}
  520         ),
  521         ks_flask.construct_resource_map(
  522             resource=ProjectTagResource,
  523             url='/projects/<string:project_id>/tags/<string:value>',
  524             resource_kwargs={},
  525             rel='project_tags',
  526             path_vars={
  527                 'project_id': json_home.Parameters.PROJECT_ID,
  528                 'value': json_home.Parameters.TAG_VALUE}
  529         ),
  530         ks_flask.construct_resource_map(
  531             resource=ProjectUserGrantResource,
  532             url=('/projects/<string:project_id>/users/<string:user_id>/'
  533                  'roles/<string:role_id>'),
  534             resource_kwargs={},
  535             rel='project_user_role',
  536             path_vars={
  537                 'project_id': json_home.Parameters.PROJECT_ID,
  538                 'user_id': json_home.Parameters.USER_ID,
  539                 'role_id': json_home.Parameters.ROLE_ID
  540             },
  541         ),
  542         ks_flask.construct_resource_map(
  543             resource=ProjectUserListGrantResource,
  544             url='/projects/<string:project_id>/users/<string:user_id>/roles',
  545             resource_kwargs={},
  546             rel='project_user_roles',
  547             path_vars={
  548                 'project_id': json_home.Parameters.PROJECT_ID,
  549                 'user_id': json_home.Parameters.USER_ID
  550             }
  551         ),
  552         ks_flask.construct_resource_map(
  553             resource=ProjectGroupGrantResource,
  554             url=('/projects/<string:project_id>/groups/<string:group_id>/'
  555                  'roles/<string:role_id>'),
  556             resource_kwargs={},
  557             rel='project_group_role',
  558             path_vars={
  559                 'project_id': json_home.Parameters.PROJECT_ID,
  560                 'group_id': json_home.Parameters.GROUP_ID,
  561                 'role_id': json_home.Parameters.ROLE_ID
  562             },
  563         ),
  564         ks_flask.construct_resource_map(
  565             resource=ProjectGroupListGrantResource,
  566             url='/projects/<string:project_id>/groups/<string:group_id>/roles',
  567             resource_kwargs={},
  568             rel='project_group_roles',
  569             path_vars={
  570                 'project_id': json_home.Parameters.PROJECT_ID,
  571                 'group_id': json_home.Parameters.GROUP_ID
  572             },
  573         ),
  574     ]
  575 
  576 
  577 APIs = (ProjectAPI,)