Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- diff --git a/api-ref/source/v3/inherit.inc b/api-ref/source/v3/inherit.inc
- index 56924409d..f959c818f 100644
- --- a/api-ref/source/v3/inherit.inc
- +++ b/api-ref/source/v3/inherit.inc
- @@ -18,7 +18,7 @@ Assign role to user on projects owned by domain
- .. rest_method:: PUT /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/index.html#assign-role-to-user-owned-by-domain-projects``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
- Assigns a role to a user in projects owned by a domain.
- @@ -43,7 +43,7 @@ Assign role to group on projects owned by a domain
- .. rest_method:: PUT /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-group-in-domain-projects``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
- The inherited role is only applied to the owned projects (both existing and
- future projects), and will not appear as a role in a domain scoped token.
- @@ -66,7 +66,7 @@ List user's inherited project roles on a domain
- .. rest_method:: GET /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-project-roles-for-user-in-domain``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_roles_inherited_to_projects``
- The list only contains those role assignments to the domain that were specified
- as being inherited to projects within that domain.
- @@ -94,7 +94,7 @@ List group's inherited project roles on domain
- .. rest_method:: GET /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-project-roles-for-group-in-domain``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_roles_inherited_to_projects``
- The list only contains those role assignments to the domain that were specified
- as being inherited to projects within that domain.
- @@ -122,7 +122,7 @@ Check if user has an inherited project role on domain
- .. rest_method:: HEAD /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-project-role-for-user-in-domain``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
- Checks whether a user has an inherited project role in a domain.
- @@ -144,7 +144,7 @@ Check if group has an inherited project role on domain
- .. rest_method:: HEAD /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-project-role-for-group-in-domain``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
- Checks whether a group has an inherited project role in a domain.
- @@ -166,7 +166,7 @@ Revoke an inherited project role from user on domain
- .. rest_method:: DELETE /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-user``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
- Revokes an inherited project role from a user in a domain.
- @@ -188,7 +188,7 @@ Revoke an inherited project role from group on domain
- .. rest_method:: DELETE /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-project-role-from-group-in-domain``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
- Revokes an inherited project role from a group in a domain.
- @@ -210,7 +210,7 @@ Assign role to user on projects in a subtree
- .. rest_method:: PUT /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-user``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
- The inherited role assignment is anchored to a project and applied to its
- subtree in the projects hierarchy (both existing and future projects).
- @@ -237,7 +237,7 @@ Assign role to group on projects in a subtree
- .. rest_method:: PUT /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-group``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
- The inherited role assignment is anchored to a project and applied to its
- subtree in the projects hierarchy (both existing and future projects).
- @@ -258,69 +258,13 @@ Request
- - role_id: role_id_path
- -List user's inherited project roles on project
- -==============================================
- -
- -.. rest_method:: GET /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/inherited_to_projects
- -
- -Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-inherited-roles-for-user``
- -
- -The list only contains those roles assigned to this project that were specified
- -as being inherited to its subtree.
- -
- -Normal response codes: 200
- -
- -Request
- --------
- -
- -.. rest_parameters:: parameters.yaml
- -
- - - project_id: project_id_path
- - - user_id: user_id_path
- -
- -Response Example
- -----------------
- -
- -.. literalinclude:: samples/admin/user-roles-list-response.json
- - :language: javascript
- -
- -
- -List group's inherited project roles on project
- -===============================================
- -
- -.. rest_method:: GET /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/inherited_to_projects
- -
- -Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-roles-for-group``
- -
- -The list only contains those roles assigned to this project that were specified
- -as being inherited to its subtree.
- -
- -Normal response codes: 200
- -
- -Request
- --------
- -
- -.. rest_parameters:: parameters.yaml
- -
- - - group_id: group_id_path
- - - project_id: project_id_path
- -
- -Response Example
- -----------------
- -
- -.. literalinclude:: samples/admin/group-roles-list-response.json
- - :language: javascript
- -
- -
- Check if user has an inherited project role on project
- ======================================================
- .. rest_method:: HEAD /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-role-for-user``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
- Checks whether a user has a role assignment with the ``inherited_to_projects`` flag in a project.
- @@ -342,7 +286,7 @@ Check if group has an inherited project role on project
- .. rest_method:: HEAD /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-role-for-group``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
- Checks whether a group has a role assignment with the ``inherited_to_projects`` flag in a project.
- @@ -364,7 +308,7 @@ Revoke an inherited project role from user on project
- .. rest_method:: DELETE /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-user``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
- Normal response codes: 204
- @@ -384,7 +328,7 @@ Revoke an inherited project role from group on project
- .. rest_method:: DELETE /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-group``
- +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
- Normal response codes: 204
- @@ -404,7 +348,7 @@ List effective role assignments
- .. rest_method:: GET /v3/role_assignments
- Relationship:
- -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-effective-role-assignments``
- +``http://docs.openstack.org/api/openstack-identity/3/rel/role_assignments``
- Optional query parameters:
- diff --git a/api-ref/source/v3/samples/admin/group-roles-list-response.json b/api-ref/source/v3/samples/admin/group-roles-list-response.json
- deleted file mode 100644
- index 21861d22b..000000000
- --- a/api-ref/source/v3/samples/admin/group-roles-list-response.json
- +++ /dev/null
- @@ -1,23 +0,0 @@
- -{
- - "roles": [
- - {
- - "id": "91011",
- - "links": {
- - "self": "http://example.com/identity/v3/roles/91011"
- - },
- - "name": "admin"
- - },
- - {
- - "id": "91011",
- - "links": {
- - "self": "http://example.com/identity/v3/roles/91011"
- - },
- - "name": "admin"
- - }
- - ],
- - "links": {
- - "self": "http://example.com/identity/v3/OS-INHERIT/projects/1234/groups/5678/roles/inherited_to_projects",
- - "previous": null,
- - "next": null
- - }
- -}
- diff --git a/api-ref/source/v3/samples/admin/user-roles-list-response.json b/api-ref/source/v3/samples/admin/user-roles-list-response.json
- deleted file mode 100644
- index 9740be6cf..000000000
- --- a/api-ref/source/v3/samples/admin/user-roles-list-response.json
- +++ /dev/null
- @@ -1,23 +0,0 @@
- -{
- - "roles": [
- - {
- - "id": "91011",
- - "links": {
- - "self": "http://example.com/identity/v3/roles/91011"
- - },
- - "name": "admin"
- - },
- - {
- - "id": "91011",
- - "links": {
- - "self": "http://example.com/identity/v3/roles/91011"
- - },
- - "name": "admin"
- - }
- - ],
- - "links": {
- - "self": "http://example.com/identity/v3/OS-INHERIT/projects/1234/users/5678/roles/inherited_to_projects",
- - "previous": null,
- - "next": null
- - }
- -}
- diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py
- index 6a6717a35..cd3842d8d 100644
- --- a/keystone/assignment/core.py
- +++ b/keystone/assignment/core.py
- @@ -588,7 +588,7 @@ class Manager(manager.Manager):
- indirect['domain_id'] = ref.pop('domain_id')
- ref['project_id'] = project_id
- - ref.pop('inherited_to_projects')
- + ref.pop('inherited_to_projects', None)
- return ref
- @@ -652,7 +652,12 @@ class Manager(manager.Manager):
- ref, user_id, project_id, subtree_ids, expand_groups)
- elif 'group_id' in ref and expand_groups:
- return expand_group_assignment(ref, user_id)
- - return [ref]
- + refs = [ref]
- + if CONF.projects_hierarchy.enabled:
- + LOG.debug("use projects hierarchy refs")
- + refs += expand_inherited_assignment(
- + ref, user_id, project_id, None, expand_groups)
- + return refs
- def add_implied_roles(self, role_refs):
- """Expand out implied roles.
- @@ -847,7 +852,19 @@ class Manager(manager.Manager):
- role_id=role_id, user_id=user_id, group_ids=group_ids,
- inherited_to_projects=True)
- - return non_inherited_refs + inherited_refs
- + result_refs = non_inherited_refs + inherited_refs
- + if project_id and CONF.projects_hierarchy.enabled:
- + # List inherited assignments from the parent projects
- + parents_ids = self.resource_api.list_project_parents_ids(
- + project_id)['parents_ids']
- + LOG.debug("get refs from parents: %s", parents_ids)
- + base_refs = self.driver.list_role_assignments(
- + role_id=role_id, user_id=user_id, group_ids=group_ids,
- + project_ids=parents_ids,
- + inherited_to_projects=inherited)
- + result_refs += base_refs
- +
- + return result_refs
- # If filtering by group or inherited domain assignment the list is
- # guaranteed to be empty
- diff --git a/keystone/common/controller.py b/keystone/common/controller.py
- index c32c77749..1a349a9e3 100644
- --- a/keystone/common/controller.py
- +++ b/keystone/common/controller.py
- @@ -718,6 +718,14 @@ class V3Controller(wsgi.Application):
- _LW('No domain information specified as part of list request'))
- raise exception.Unauthorized()
- + def _get_project_id_from_token(self, request):
- + """Get the project_id for a v3 list call."""
- + token_ref = utils.get_token_ref(request.context_dict)
- + if token_ref.project_scoped:
- + return token_ref.project_id
- + LOG.warning(_LW('No project information for project aware call'))
- + raise exception.Unauthorized()
- +
- def _get_domain_id_from_token(self, request):
- """Get the domain_id for a v3 create call.
- @@ -799,6 +807,17 @@ class V3Controller(wsgi.Application):
- utils.flatten_dict(policy_dict))
- LOG.debug('RBAC: Authorization granted')
- + def check_group_ownership(self, request, ref):
- + # we use project_id as domain id, but keep original domain_scope
- + if ref['domain_id'] != self._get_project_id_from_token(request):
- + raise exception.Forbidden()
- +
- + def check_user_ownership(self, request, ref):
- + # we use project_id as domain id, but keep original domain_scope
- + scope_project_id = self._get_project_id_from_token(request)
- + if ref.get('default_project_id') != scope_project_id:
- + raise exception.Forbidden()
- +
- @classmethod
- def filter_params(cls, ref):
- """Remove unspecified parameters from the dictionary.
- @@ -814,3 +833,7 @@ class V3Controller(wsgi.Application):
- for blocked_param in blocked_keys:
- del ref[blocked_param]
- return ref
- +
- + def need_filter_by_projects_hierarchy(self, request):
- + """Check that request response should be filtered."""
- + return not request.context.is_admin and CONF.projects_hierarchy.enabled
- diff --git a/keystone/common/utils.py b/keystone/common/utils.py
- index 096e077b5..af093daf9 100644
- --- a/keystone/common/utils.py
- +++ b/keystone/common/utils.py
- @@ -156,7 +156,7 @@ def check_password(password, hashed):
- return passlib.hash.sha512_crypt.verify(password_utf8, hashed)
- -def attr_as_boolean(val_attr):
- +def attr_as_boolean(val_attr, default=True):
- """Return the boolean value, decoded from a string.
- We test explicitly for a value meaning False, which can be one of
- @@ -165,7 +165,7 @@ def attr_as_boolean(val_attr):
- meaning True.
- """
- - return strutils.bool_from_string(val_attr, default=True)
- + return strutils.bool_from_string(val_attr, default=default)
- def get_blob_from_credential(credential):
- diff --git a/keystone/conf/__init__.py b/keystone/conf/__init__.py
- index eb08edf45..78468057e 100644
- --- a/keystone/conf/__init__.py
- +++ b/keystone/conf/__init__.py
- @@ -39,6 +39,7 @@ from keystone.conf import oauth1
- from keystone.conf import os_inherit
- from keystone.conf import paste_deploy
- from keystone.conf import policy
- +from keystone.conf import projects_hierarchy
- from keystone.conf import resource
- from keystone.conf import revoke
- from keystone.conf import role
- @@ -75,6 +76,7 @@ conf_modules = [
- os_inherit,
- paste_deploy,
- policy,
- + projects_hierarchy,
- resource,
- revoke,
- role,
- diff --git a/keystone/conf/projects_hierarchy.py b/keystone/conf/projects_hierarchy.py
- new file mode 100644
- index 000000000..ec140014e
- --- /dev/null
- +++ b/keystone/conf/projects_hierarchy.py
- @@ -0,0 +1,37 @@
- +# Licensed under the Apache License, Version 2.0 (the "License"); you may
- +# not use this file except in compliance with the License. You may obtain
- +# a copy of the License at
- +#
- +# http://www.apache.org/licenses/LICENSE-2.0
- +#
- +# Unless required by applicable law or agreed to in writing, software
- +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- +# License for the specific language governing permissions and limitations
- +# under the License.
- +
- +from oslo_config import cfg
- +
- +from keystone.conf import utils
- +
- +
- +enabled = cfg.BoolOpt(
- + 'enabled',
- + default=False,
- + help=utils.fmt("""
- +This allows automatic access to child projects within hierarchy.
- +"""))
- +
- +
- +GROUP_NAME = __name__.split('.')[-1]
- +ALL_OPTS = [
- + enabled,
- +]
- +
- +
- +def register_opts(conf):
- + conf.register_opts(ALL_OPTS, group=GROUP_NAME)
- +
- +
- +def list_opts():
- + return {GROUP_NAME: ALL_OPTS}
- diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
- index 4f161ee17..4f790e8e9 100644
- --- a/keystone/identity/controllers.py
- +++ b/keystone/identity/controllers.py
- @@ -200,6 +200,10 @@ class UserV3(controller.V3Controller):
- ref = {}
- ref['user'] = self.identity_api.get_user(user_id)
- ref['group'] = self.identity_api.get_group(group_id)
- + # allow access for cloud_admin
- + if self.need_filter_by_projects_hierarchy(request):
- + # check it here because, it is more convenient
- + self.check_user_ownership(request, ref['user'])
- self.check_protection(request, prep_info, ref)
- def _check_group_protection(self, request, prep_info, group_id):
- @@ -213,6 +217,15 @@ class UserV3(controller.V3Controller):
- # The manager layer will generate the unique ID for users
- ref = self._normalize_dict(user)
- ref = self._normalize_domain_id(request, ref)
- + if CONF.projects_hierarchy.enabled:
- + LOG.debug("create user %s", ref)
- + if request.context.is_admin and not ref.get('default_project_id'):
- + raise exception.ValidationError(
- + attribute='default_project_id', target=user)
- + if not request.context.is_admin:
- + scope_project_id = self._get_project_id_from_token(request)
- + ref['default_project_id'] = scope_project_id
- +
- initiator = notifications._get_request_audit_info(request.context_dict)
- ref = self.identity_api.create_user(ref, initiator)
- return UserV3.wrap_member(request.context_dict, ref)
- @@ -220,6 +233,9 @@ class UserV3(controller.V3Controller):
- @controller.filterprotected('domain_id', 'enabled', 'name')
- def list_users(self, request, filters):
- hints = UserV3.build_driver_hints(request, filters)
- + if self.need_filter_by_projects_hierarchy(request):
- + hints.add_filter(
- + 'default_project_id', self._get_project_id_from_token(request))
- domain = self._get_domain_id_for_list_request(request)
- refs = self.identity_api.list_users(domain_scope=domain, hints=hints)
- return UserV3.wrap_collection(request.context_dict, refs, hints=hints)
- @@ -229,11 +245,16 @@ class UserV3(controller.V3Controller):
- def list_users_in_group(self, request, filters, group_id):
- hints = UserV3.build_driver_hints(request, filters)
- refs = self.identity_api.list_users_in_group(group_id, hints=hints)
- + if self.need_filter_by_projects_hierarchy(request):
- + proj = self._get_project_id_from_token(request)
- + refs = [x for x in refs if x.get('default_project_id') == proj]
- return UserV3.wrap_collection(request.context_dict, refs, hints=hints)
- @controller.protected()
- def get_user(self, request, user_id):
- ref = self.identity_api.get_user(user_id)
- + if self.need_filter_by_projects_hierarchy(request):
- + self.check_user_ownership(request, ref)
- return UserV3.wrap_member(request.context_dict, ref)
- def _update_user(self, context, user_id, user):
- @@ -247,6 +268,9 @@ class UserV3(controller.V3Controller):
- @controller.protected()
- def update_user(self, request, user_id, user):
- validation.lazy_validate(schema.user_update, user)
- + if self.need_filter_by_projects_hierarchy(request):
- + ref = self.identity_api.get_user(user_id)
- + self.check_user_ownership(request, ref)
- return self._update_user(request.context_dict, user_id, user)
- @controller.protected(callback=_check_user_and_group_protection)
- @@ -265,11 +289,17 @@ class UserV3(controller.V3Controller):
- @controller.protected()
- def delete_user(self, request, user_id):
- + if self.need_filter_by_projects_hierarchy(request):
- + ref = self.identity_api.get_user(user_id)
- + self.check_user_ownership(request, ref)
- initiator = notifications._get_request_audit_info(request.context_dict)
- return self.identity_api.delete_user(user_id, initiator)
- @controller.protected()
- def change_password(self, request, user_id, user):
- + if self.need_filter_by_projects_hierarchy(request):
- + ref = self.identity_api.get_user(user_id)
- + self.check_user_ownership(request, ref)
- original_password = user.get('original_password')
- if original_password is None:
- raise exception.ValidationError(target='user',
- @@ -299,6 +329,8 @@ class GroupV3(controller.V3Controller):
- ref = {}
- ref['user'] = self.identity_api.get_user(user_id)
- self.check_protection(request, prep_info, ref)
- + if self.need_filter_by_projects_hierarchy(request):
- + self.check_user_ownership(request, ref['user'])
- @controller.protected()
- def create_group(self, request, group):
- diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py
- index 2523e8dee..0746865a2 100644
- --- a/keystone/resource/controllers.py
- +++ b/keystone/resource/controllers.py
- @@ -15,12 +15,16 @@
- """Workflow Logic the Resource service."""
- +import copy
- import uuid
- from six.moves import http_client
- +from oslo_log import log
- +
- from keystone.common import controller
- from keystone.common import dependency
- +from keystone.common import utils
- from keystone.common import validation
- from keystone.common import wsgi
- import keystone.conf
- @@ -31,6 +35,7 @@ from keystone.resource import schema
- CONF = keystone.conf.CONF
- +LOG = log.getLogger(__name__)
- @dependency.requires('resource_api')
- @@ -235,11 +240,21 @@ class ProjectV3(controller.V3Controller):
- if not ref.get('is_domain'):
- ref = self._normalize_domain_id(request, ref)
- - # Our API requires that you specify the location in the hierarchy
- - # unambiguously. This could be by parent_id or, if it is a top level
- - # project, just by providing a domain_id.
- - if not ref.get('parent_id'):
- - ref['parent_id'] = ref.get('domain_id')
- +
- + if CONF.projects_hierarchy.enabled:
- + if request.context.is_admin and not ref.get('parent_id'):
- + raise exception.ValidationError(
- + attribute='parent_id', target=ref)
- + if not request.context.is_admin:
- + token_ref = utils.get_token_ref(request.context_dict)
- + ref['parent_id'] = token_ref.project_id
- + ref['domain_id'] = token_ref.project_domain_id
- + else:
- + # Our API requires that you specify the location in the hierarchy
- + # unambiguously. This could be by parent_id or,
- + # if it is a top level project, just by providing a domain_id.
- + if not ref.get('parent_id'):
- + ref['parent_id'] = ref.get('domain_id')
- initiator = notifications._get_request_audit_info(request.context_dict)
- try:
- @@ -257,7 +272,28 @@ class ProjectV3(controller.V3Controller):
- # False (which in query terms means '0'
- if 'is_domain' not in request.params:
- hints.add_filter('is_domain', '0')
- - refs = self.resource_api.list_projects(hints=hints)
- +
- + search_domains = utils.attr_as_boolean(
- + hints.get_exact_filter_by_name('is_domain')['value'],
- + default=False
- + )
- + refs = []
- + effective_hints = hints
- + if (not search_domains and
- + self.need_filter_by_projects_hierarchy(request)):
- + token_project_id = self._get_project_id_from_token(request)
- + parent_id = hints.get_exact_filter_by_name('parent_id')
- + if parent_id and parent_id['value'] != token_project_id:
- + # it is prohibit to require children from other scope
- + return []
- + if not parent_id:
- + effective_hints = copy.deepcopy(hints)
- + local_hints = copy.deepcopy(hints)
- + effective_hints.add_filter('parent_id', token_project_id)
- + local_hints.add_filter('id', token_project_id)
- + refs = self.resource_api.list_projects(hints=local_hints)
- +
- + refs += self.resource_api.list_projects(hints=effective_hints)
- return ProjectV3.wrap_collection(request.context_dict,
- refs, hints=hints)
- @@ -304,18 +340,35 @@ class ProjectV3(controller.V3Controller):
- ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
- ref['id'])
- + def _check_ownership(self, request, project_id, ref=None):
- + if self.need_filter_by_projects_hierarchy(request):
- + # only direct children and context project itself are accessible.
- + if ref is None:
- + ref = self.resource_api.get_project(project_id)
- + token_project_id = self._get_project_id_from_token(request)
- + if token_project_id not in [ref['id'], ref['parent_id']]:
- + raise exception.Forbidden()
- +
- @controller.protected()
- def get_project(self, request, project_id):
- ref = self.resource_api.get_project(project_id)
- + self._check_ownership(request, project_id, ref)
- self._expand_project_ref(request, ref)
- return ProjectV3.wrap_member(request.context_dict, ref)
- @controller.protected()
- def update_project(self, request, project_id, project):
- + self._check_ownership(request, project_id)
- validation.lazy_validate(schema.project_update, project)
- self._require_matching_id(project_id, project)
- self._require_matching_domain_id(
- project_id, project, self.resource_api.get_project)
- + if CONF.projects_hierarchy.enabled and project.get('parent_id'):
- + # in this case parent_id acts like domain_id
- + existing_ref = self.resource_api.get_project(project_id)
- + if project['parent_id'] != existing_ref['parent_id']:
- + raise exception.ValidationError(_('Cannot change Parent ID'))
- +
- initiator = notifications._get_request_audit_info(request.context_dict)
- ref = self.resource_api.update_project(project_id, project,
- initiator=initiator)
- @@ -323,6 +376,7 @@ class ProjectV3(controller.V3Controller):
- @controller.protected()
- def delete_project(self, request, project_id):
- + self._check_ownership(request, project_id)
- initiator = notifications._get_request_audit_info(request.context_dict)
- return self.resource_api.delete_project(project_id,
- initiator=initiator)
- diff --git a/keystone/resource/core.py b/keystone/resource/core.py
- index abd4e89cc..6bfa96035 100644
- --- a/keystone/resource/core.py
- +++ b/keystone/resource/core.py
- @@ -561,13 +561,25 @@ class Manager(manager.Manager):
- def list_project_parents(self, project_id, user_id=None):
- self._assert_valid_project_id(project_id)
- - parents = self.driver.list_project_parents(project_id)
- + parents_ids = self.list_project_parents_ids(project_id)['parents_ids']
- + parents = self.driver.list_projects_from_ids(parents_ids)
- # If a user_id was provided, the returned list should be filtered
- # against the projects this user has access to.
- if user_id:
- parents = self._filter_projects_list(parents, user_id)
- return parents
- + @MEMOIZE
- + def list_project_parents_ids(self, project_id):
- + if project_id is None:
- + msg = _('Project field is required and cannot be empty.')
- + raise exception.ValidationError(message=msg)
- + return {
- + 'parents_ids': [
- + x['id'] for x in self.driver.list_project_parents(project_id)
- + ]
- + }
- +
- def _build_parents_as_ids_dict(self, project, parents_by_id):
- # NOTE(rodrigods): we don't rely in the order of the projects returned
- # by the list_project_parents() method. Thus, we create a project cache
- diff --git a/keystone/tests/unit/assignment/test_backends.py b/keystone/tests/unit/assignment/test_backends.py
- index 019442cfb..cd1b56af5 100644
- --- a/keystone/tests/unit/assignment/test_backends.py
- +++ b/keystone/tests/unit/assignment/test_backends.py
- @@ -1647,6 +1647,74 @@ class AssignmentTests(AssignmentTestHelperMixin):
- user_id=self.user_foo['id'],
- source_from_group_ids=[group['id']])
- + def test_list_role_assignments_for_projects_hierarchy(self):
- + """Test listing of role assignments retrieved from hierarchy."""
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + root_project = unit.new_project_ref(
- + domain_id=CONF.identity.default_domain_id)
- + root_project = self.resource_api.create_project(root_project['id'],
- + root_project)
- + leaf_project = unit.new_project_ref(
- + domain_id=CONF.identity.default_domain_id,
- + parent_id=root_project['id'])
- + leaf_project = self.resource_api.create_project(leaf_project['id'],
- + leaf_project)
- +
- + user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
- + user = self.identity_api.create_user(user)
- +
- + leaf_user = unit.new_user_ref(
- + domain_id=CONF.identity.default_domain_id)
- + leaf_user = self.identity_api.create_user(leaf_user)
- +
- + # Grant root project user role
- + self.assignment_api.create_grant(user_id=user['id'],
- + project_id=root_project['id'],
- + role_id=self.role_admin['id'])
- + self.assignment_api.create_grant(user_id=user['id'],
- + project_id=root_project['id'],
- + role_id=self.role_member['id'])
- +
- + # Grant leaf project user role
- + self.assignment_api.create_grant(user_id=leaf_user['id'],
- + project_id=leaf_project['id'],
- + role_id=self.role_admin['id'])
- +
- + # Should get back both projects: because the direct role assignment for
- + # the root project and inherited role assignment for leaf project
- + user_projects = self.assignment_api.list_projects_for_user(user['id'])
- + self.assertEqual(2, len(user_projects))
- + self.assertIn(root_project, user_projects)
- + self.assertIn(leaf_project, user_projects)
- +
- + leaf_projects = self.assignment_api.list_projects_for_user(
- + leaf_user['id'])
- + self.assertEqual(1, len(leaf_projects))
- + self.assertIn(leaf_project, user_projects)
- +
- + user_roles = self.assignment_api.get_roles_for_user_and_project(
- + user['id'], root_project['id'])
- + self.assertEqual(2, len(user_roles))
- +
- + user_roles = self.assignment_api.get_roles_for_user_and_project(
- + user['id'], leaf_project['id'])
- + self.assertEqual(2, len(user_roles))
- +
- + leaf_roles = self.assignment_api.get_roles_for_user_and_project(
- + leaf_user['id'], leaf_project['id'])
- + self.assertEqual(1, len(leaf_roles))
- +
- + leaf_roles = self.assignment_api.get_roles_for_user_and_project(
- + leaf_user['id'], root_project['id'])
- + self.assertEqual(0, len(leaf_roles))
- +
- + # Disable projects_hiearchy extension
- + self.config_fixture.config(group='projects_hierarchy', enabled=False)
- + # Should get back just root project - due the direct role assignment
- + user_projects = self.assignment_api.list_projects_for_user(user['id'])
- + self.assertEqual(1, len(user_projects))
- + self.assertIn(root_project, user_projects)
- +
- def test_add_user_to_project(self):
- self.assignment_api.add_user_to_project(self.tenant_baz['id'],
- self.user_foo['id'])
- diff --git a/keystone/tests/unit/test_v3.py b/keystone/tests/unit/test_v3.py
- index 5db673012..19292d4e9 100644
- --- a/keystone/tests/unit/test_v3.py
- +++ b/keystone/tests/unit/test_v3.py
- @@ -226,7 +226,8 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase,
- 'name': {'type': 'string'}
- },
- 'additionalProperties': False
- - }
- + },
- + 'parents_ids': {'type': 'array'}
- },
- 'additionalProperties': False
- }
- diff --git a/keystone/tests/unit/test_v3_auth.py b/keystone/tests/unit/test_v3_auth.py
- index 7cd31b1bc..56c874777 100644
- --- a/keystone/tests/unit/test_v3_auth.py
- +++ b/keystone/tests/unit/test_v3_auth.py
- @@ -658,6 +658,18 @@ class TokenAPITests(object):
- r = self.v3_create_token(auth_data)
- self.assertValidProjectScopedTokenResponse(r)
- + def test_create_project_scoped_token_with_parents_ids(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + auth_data = self.build_authentication_request(
- + user_id=self.user['id'],
- + password=self.user['password'],
- + project_id=self.project['id'])
- + pref = self.resource_api.list_project_parents_ids(self.project['id'])
- + r = self.v3_create_token(auth_data)
- + self.assertValidProjectScopedTokenResponse(r)
- + self.assertEqual(
- + pref['parents_ids'], r.result['token']['project']['parents_ids'])
- +
- def test_validate_project_scoped_token(self):
- project_scoped_token = self._get_project_scoped_token()
- r = self._validate_token(project_scoped_token)
- diff --git a/keystone/tests/unit/test_v3_identity.py b/keystone/tests/unit/test_v3_identity.py
- index cbfb5aab3..451d33051 100644
- --- a/keystone/tests/unit/test_v3_identity.py
- +++ b/keystone/tests/unit/test_v3_identity.py
- @@ -68,6 +68,78 @@ class IdentityTestCaseStaticAdminToken(test_v3.RestfulTestCase):
- self.post('/users', body={'user': ref}, token=CONF.admin_token,
- expected_status=http_client.BAD_REQUEST)
- + def test_projects_hierarchy_create_user_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_user_ref(domain_id=self.domain_id)
- + self.post('/users', body={'user': ref},
- + token=CONF.admin_token,
- + expected_status=http_client.BAD_REQUEST)
- +
- + ref['default_project_id'] = self.project_id
- + self.post('/users', body={'user': ref}, token=CONF.admin_token)
- +
- + def test_projects_hierarchy_list_users_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + users = self.get('/users', token=CONF.admin_token).result
- + users_ids = [x['id'] for x in users['users']]
- + self.assertIn(user['id'], users_ids)
- +
- + def test_projects_hierarchy_get_user_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + self.get('/users/%s' % user['id'], token=CONF.admin_token)
- +
- + def test_projects_hierarchy_update_user_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + self.patch('/users/%s' % user['id'],
- + body={'user': {'enabled': False}},
- + token=CONF.admin_token)
- +
- + def test_projects_hierarchy_delete_user_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + self.delete('/users/%s' % user['id'], token=CONF.admin_token)
- +
- + def test_projects_hierarchy_list_users_in_group_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + group = unit.new_group_ref(domain_id=self.domain_id)
- + group = self.identity_api.create_group(group)
- + self.identity_api.add_user_to_group(user['id'], group['id'])
- + self.identity_api.add_user_to_group(self.user_id, group['id'])
- + r = self.get('/groups/%s/users' % group['id'],
- + token=CONF.admin_token).result
- + users_ids = [x['id'] for x in r['users']]
- + self.assertIn(user['id'], users_ids)
- + self.assertIn(self.user_id, users_ids)
- +
- class IdentityTestCase(test_v3.RestfulTestCase):
- """Test users and groups."""
- @@ -668,6 +740,125 @@ class IdentityTestCase(test_v3.RestfulTestCase):
- token=CONF.admin_token,
- expected_status=http_client.BAD_REQUEST)
- + def test_projects_hierarchy_create_user(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_user_ref(domain_id=self.domain_id)
- + # it is prohibited to create user without project_scope
- + self.post('/users',
- + body={'user': ref},
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN)
- +
- + # create user with project scoped token
- + user = self.post('/users',
- + body={'user': ref},
- + token=self.get_scoped_token()).result['user']
- + self.assertEqual(self.project_id, user['default_project_id'])
- +
- + def test_projects_hierarchy_list_users(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + project_user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- + users = self.get('/users', token=self.get_scoped_token()).result
- + users_ids = [x['id'] for x in users['users']]
- + self.assertNotIn(user['id'], users_ids)
- + self.assertIn(project_user['id'], users_ids)
- +
- + def test_projects_hierarchy_get_user(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + project_user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- + self.get('/users/%s' % user['id'],
- + token=self.get_scoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.get('/users/%s' % project_user['id'],
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.get('/users/%s' % project_user['id'],
- + token=self.get_scoped_token())
- +
- + def test_projects_hierarchy_update_user(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + project_user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- + self.patch('/users/%s' % user['id'],
- + body={'user': {'enabled': False}},
- + token=self.get_scoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.patch('/users/%s' % project_user['id'],
- + body={'user': {'enabled': False}},
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.patch('/users/%s' % project_user['id'],
- + body={'user': {'enabled': False}},
- + token=self.get_scoped_token())
- +
- + def test_projects_hierarchy_delete_user(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + project_user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- + self.delete('/users/%s' % user['id'],
- + token=self.get_scoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.delete('/users/%s' % project_user['id'],
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + self.delete('/users/%s' % project_user['id'],
- + token=self.get_scoped_token())
- +
- + def test_projects_hierarchy_list_users_in_group(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- + user2 = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + self.identity_api.add_user_to_group(user['id'], self.group_id)
- + self.identity_api.add_user_to_group(user2['id'], self.group_id)
- + self.get('/groups/%s/users' % self.group_id,
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN)
- + r = self.get('/groups/%s/users' % self.group_id,
- + token=self.get_scoped_token()).result
- + self.assertEqual([user['id']], [x['id'] for x in r['users']])
- +
- class IdentityV3toV2MethodsTestCase(unit.TestCase):
- """Test users V3 to V2 conversion methods."""
- @@ -892,6 +1083,36 @@ class UserSelfServiceChangingPasswordsTestCase(test_v3.RestfulTestCase):
- self.assertNotIn(self.user_ref['password'], log_fix.output)
- self.assertNotIn(new_password, log_fix.output)
- + def test_projects_hierarchy_changing_password(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- +
- + def change_password(user_ref, token, expected_status):
- + update = {
- + 'password': uuid.uuid4().hex,
- + 'original_password': user_ref['password'],
- + }
- + self.post('/users/%s/password' % user_ref['id'],
- + body={'user': update},
- + token=token,
- + expected_status=expected_status)
- +
- + project = unit.new_project_ref(domain_id=self.domain_id)
- + self.resource_api.create_project(project['id'], project)
- + user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=project['id']
- + )
- + project_user = unit.create_user(
- + self.identity_api,
- + domain_id=self.domain_id, project_id=self.project_id
- + )
- +
- + change_password(user, self.get_scoped_token(), http_client.FORBIDDEN)
- + change_password(
- + project_user, self.get_unscoped_token(), http_client.FORBIDDEN)
- + change_password(
- + project_user, self.get_scoped_token(), http_client.NO_CONTENT)
- +
- class PasswordValidationTestCase(UserSelfServiceChangingPasswordsTestCase):
- """Test password validation."""
- diff --git a/keystone/tests/unit/test_v3_resource.py b/keystone/tests/unit/test_v3_resource.py
- index bffe54642..2b2e66bfa 100644
- --- a/keystone/tests/unit/test_v3_resource.py
- +++ b/keystone/tests/unit/test_v3_resource.py
- @@ -29,7 +29,35 @@ from keystone.tests.unit import utils as test_utils
- CONF = keystone.conf.CONF
- -class ResourceTestCase(test_v3.RestfulTestCase,
- +class ResourseRestTestCase(test_v3.RestfulTestCase):
- + def _create_projects_hierarchy(self, hierarchy_size=1):
- + """Create a single-branched project hierarchy with the specified size.
- +
- + :param hierarchy_size: the desired hierarchy size, default is 1 -
- + a project with one child.
- +
- + :returns projects: a list of the projects in the created hierarchy.
- +
- + """
- + new_ref = unit.new_project_ref(domain_id=self.domain_id)
- + resp = self.post('/projects', body={'project': new_ref})
- +
- + projects = [resp.result]
- +
- + for i in range(hierarchy_size):
- + new_ref = unit.new_project_ref(
- + domain_id=self.domain_id,
- + parent_id=projects[i]['project']['id'])
- + resp = self.post('/projects',
- + body={'project': new_ref})
- + self.assertValidProjectResponse(resp, new_ref)
- +
- + projects.append(resp.result)
- +
- + return projects
- +
- +
- +class ResourceTestCase(ResourseRestTestCase,
- test_v3.AssignmentTestMixin):
- """Test domains and projects."""
- @@ -686,32 +714,6 @@ class ResourceTestCase(test_v3.RestfulTestCase,
- ref_child['domain_id'] = self.domain['id']
- self.assertValidProjectResponse(r, ref_child)
- - def _create_projects_hierarchy(self, hierarchy_size=1):
- - """Create a single-branched project hierarchy with the specified size.
- -
- - :param hierarchy_size: the desired hierarchy size, default is 1 -
- - a project with one child.
- -
- - :returns projects: a list of the projects in the created hierarchy.
- -
- - """
- - new_ref = unit.new_project_ref(domain_id=self.domain_id)
- - resp = self.post('/projects', body={'project': new_ref})
- -
- - projects = [resp.result]
- -
- - for i in range(hierarchy_size):
- - new_ref = unit.new_project_ref(
- - domain_id=self.domain_id,
- - parent_id=projects[i]['project']['id'])
- - resp = self.post('/projects',
- - body={'project': new_ref})
- - self.assertValidProjectResponse(resp, new_ref)
- -
- - projects.append(resp.result)
- -
- - return projects
- -
- def test_list_projects_filtering_by_parent_id(self):
- """Call ``GET /projects?parent_id={project_id}``."""
- projects = self._create_projects_hierarchy(hierarchy_size=2)
- @@ -1314,6 +1316,172 @@ class ResourceTestCase(test_v3.RestfulTestCase,
- 'project_id': projects[0]['project']['id']},
- expected_status=http_client.FORBIDDEN)
- + def test_projects_hierarchy_create_project(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + # parent id will be overridden
- + ref = unit.new_project_ref(
- + domain_id=self.domain_id, parent_id='unknown')
- + self.post(
- + '/projects',
- + body={'project': ref},
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN
- + )
- + r = self.post(
- + '/projects',
- + body={'project': ref},
- + token=self.get_scoped_token()
- + ).result
- + self.assertEqual(self.project_id, r['project']['parent_id'])
- +
- + def test_projects_hierarchy_list_project(self):
- + self._create_projects_hierarchy(1)
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_project_ref(domain_id=self.domain_id)
- + ref = self.post('/projects', body={'project': ref}).result['project']
- + self.get(
- + '/projects',
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN
- + )
- + r = self.get('/projects', token=self.get_scoped_token()).result
- + actual = [x['id'] for x in r['projects']]
- + expected = [self.project_id, ref['id']]
- + self.assertEqual(sorted(expected), sorted(actual))
- +
- + def test_projects_hierarchy_list_domains(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + r = self.get(
- + '/projects?is_domain=1',
- + token=self.get_scoped_token(),
- + ).result
- + ids = [x['id'] for x in r['projects']]
- + self.assertIn(self.domain_id, ids)
- +
- + def test_projects_hierarchy_get_project(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_project_ref(domain_id=self.domain_id)
- + ref = self.post('/projects', body={'project': ref}).result['project']
- + self.get(
- + '/projects/%s' % ref['id'],
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN
- + )
- + self.get('/projects/%s' % ref['id'], token=self.get_scoped_token())
- +
- + def test_projects_hierarchy_update_project(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_project_ref(domain_id=self.domain_id)
- + ref = self.post('/projects', body={'project': ref}).result['project']
- + new_ref = unit.new_project_ref(domain_id=self.domain_id)
- + del new_ref['id']
- + self.patch(
- + '/projects/%s' % ref['id'],
- + body={'project': new_ref},
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN
- + )
- + self.patch(
- + '/projects/%s' % ref['id'],
- + body={'project': new_ref},
- + token=self.get_scoped_token(),
- + )
- + new_ref['parent_id'] = self.project_id
- + self.patch(
- + '/projects/%s' % ref['id'],
- + body={'project': new_ref},
- + token=self.get_scoped_token(),
- + )
- + new_ref['parent_id'] = self.domain_id
- + self.patch(
- + '/projects/%s' % ref['id'],
- + body={'project': new_ref},
- + token=self.get_scoped_token(),
- + expected_status=http_client.BAD_REQUEST
- + )
- +
- + def test_projects_hierarchy_delete_project(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + ref = unit.new_project_ref(domain_id=self.domain_id)
- + ref = self.post('/projects', body={'project': ref}).result['project']
- + self.delete(
- + '/projects/%s' % ref['id'],
- + token=self.get_unscoped_token(),
- + expected_status=http_client.FORBIDDEN
- + )
- + self.delete('/projects/%s' % ref['id'], token=self.get_scoped_token())
- +
- +
- +class ResourceV3TestCaseStaticAdminToken(ResourseRestTestCase):
- + EXTENSION_TO_ADD = 'admin_token_auth'
- +
- + def config_overrides(self):
- + super(ResourceV3TestCaseStaticAdminToken, self).config_overrides()
- + self.config_fixture.config(
- + admin_token='ADMIN')
- +
- + def test_projects_hierarchy_create_project_as_admin(self):
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + # parent id will be overridden
- + ref = unit.new_project_ref(domain_id=self.domain_id)
- + self.post(
- + '/projects',
- + body={'project': ref},
- + token=CONF.admin_token,
- + expected_status=http_client.BAD_REQUEST
- + )
- + ref['parent_id'] = self.project_id
- + r = self.post(
- + '/projects',
- + body={'project': ref},
- + token=CONF.admin_token,
- + ).result
- + self.assertEqual(self.project_id, r['project']['parent_id'])
- +
- + def test_projects_hierarchy_list_project_as_admin(self):
- + refs = self._create_projects_hierarchy(2)
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + r = self.get('/projects', token=CONF.admin_token).result
- + actual = [x['id'] for x in r['projects']]
- + expected = [x['project']['id'] for x in refs]
- + expected.append(self.project_id)
- + expected.append(self.default_domain_project['id'])
- + self.assertEqual(sorted(expected), sorted(actual))
- +
- + def test_projects_hierarchy_get_project_as_admin(self):
- + refs = self._create_projects_hierarchy(1)
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + self.get(
- + '/projects/%s' % refs[1]['project']['id'],
- + token=CONF.admin_token,
- + )
- +
- + def test_projects_hierarchy_update_project_as_admin(self):
- + refs = self._create_projects_hierarchy(1)
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + new_ref = unit.new_project_ref(domain_id=self.domain_id)
- + del new_ref['id']
- + self.patch(
- + '/projects/%s' % refs[1]['project']['id'],
- + body={'project': new_ref},
- + token=CONF.admin_token,
- + )
- + new_ref['parent_id'] = self.domain_id
- + self.patch(
- + '/projects/%s' % refs[1]['project']['id'],
- + body={'project': new_ref},
- + token=CONF.admin_token,
- + expected_status=http_client.BAD_REQUEST
- + )
- +
- + def test_projects_hierarchy_delete_project_as_admin(self):
- + refs = self._create_projects_hierarchy(1)
- + self.config_fixture.config(group='projects_hierarchy', enabled=True)
- + self.delete(
- + '/projects/%s' % refs[1]['project']['id'],
- + token=CONF.admin_token,
- + )
- +
- class ResourceV3toV2MethodsTestCase(unit.TestCase):
- """Test domain V3 to V2 conversion methods."""
- diff --git a/keystone/token/providers/common.py b/keystone/token/providers/common.py
- index 524b9f41c..803029b2b 100644
- --- a/keystone/token/providers/common.py
- +++ b/keystone/token/providers/common.py
- @@ -289,6 +289,10 @@ class V3TokenDataHelper(object):
- else:
- # Projects acting as a domain do not have a domain_id attribute
- filtered_project['domain'] = None
- + if CONF.projects_hierarchy.enabled:
- + pref = self.resource_api.list_project_parents_ids(project_id)
- + filtered_project['parents_ids'] = pref['parents_ids']
- +
- return filtered_project
- def _populate_scope(self, token_data, domain_id, project_id):
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement