Advertisement
Guest User

Untitled

a guest
Nov 27th, 2017
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 57.90 KB | None | 0 0
  1. diff --git a/api-ref/source/v3/inherit.inc b/api-ref/source/v3/inherit.inc
  2. index 56924409d..f959c818f 100644
  3. --- a/api-ref/source/v3/inherit.inc
  4. +++ b/api-ref/source/v3/inherit.inc
  5. @@ -18,7 +18,7 @@ Assign role to user on projects owned by domain
  6. .. rest_method:: PUT /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  7.  
  8. Relationship:
  9. -``http://developer.openstack.org/api-ref/identity/v3/index.html#assign-role-to-user-owned-by-domain-projects``
  10. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
  11.  
  12. Assigns a role to a user in projects owned by a domain.
  13.  
  14. @@ -43,7 +43,7 @@ Assign role to group on projects owned by a domain
  15. .. rest_method:: PUT /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  16.  
  17. Relationship:
  18. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-group-in-domain-projects``
  19. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
  20.  
  21. The inherited role is only applied to the owned projects (both existing and
  22. future projects), and will not appear as a role in a domain scoped token.
  23. @@ -66,7 +66,7 @@ List user's inherited project roles on a domain
  24. .. rest_method:: GET /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/inherited_to_projects
  25.  
  26. Relationship:
  27. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-project-roles-for-user-in-domain``
  28. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_roles_inherited_to_projects``
  29.  
  30. The list only contains those role assignments to the domain that were specified
  31. as being inherited to projects within that domain.
  32. @@ -94,7 +94,7 @@ List group's inherited project roles on domain
  33. .. rest_method:: GET /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/inherited_to_projects
  34.  
  35. Relationship:
  36. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-project-roles-for-group-in-domain``
  37. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_roles_inherited_to_projects``
  38.  
  39. The list only contains those role assignments to the domain that were specified
  40. as being inherited to projects within that domain.
  41. @@ -122,7 +122,7 @@ Check if user has an inherited project role on domain
  42. .. rest_method:: HEAD /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  43.  
  44. Relationship:
  45. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-project-role-for-user-in-domain``
  46. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
  47.  
  48. Checks whether a user has an inherited project role in a domain.
  49.  
  50. @@ -144,7 +144,7 @@ Check if group has an inherited project role on domain
  51. .. rest_method:: HEAD /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  52.  
  53. Relationship:
  54. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-project-role-for-group-in-domain``
  55. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
  56.  
  57. Checks whether a group has an inherited project role in a domain.
  58.  
  59. @@ -166,7 +166,7 @@ Revoke an inherited project role from user on domain
  60. .. rest_method:: DELETE /v3/OS-INHERIT/domains/{domain_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  61.  
  62. Relationship:
  63. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-user``
  64. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_user_role_inherited_to_projects``
  65.  
  66. Revokes an inherited project role from a user in a domain.
  67.  
  68. @@ -188,7 +188,7 @@ Revoke an inherited project role from group on domain
  69. .. rest_method:: DELETE /v3/OS-INHERIT/domains/{domain_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  70.  
  71. Relationship:
  72. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-project-role-from-group-in-domain``
  73. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/domain_group_role_inherited_to_projects``
  74.  
  75. Revokes an inherited project role from a group in a domain.
  76.  
  77. @@ -210,7 +210,7 @@ Assign role to user on projects in a subtree
  78. .. rest_method:: PUT /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  79.  
  80. Relationship:
  81. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-user``
  82. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
  83.  
  84. The inherited role assignment is anchored to a project and applied to its
  85. subtree in the projects hierarchy (both existing and future projects).
  86. @@ -237,7 +237,7 @@ Assign role to group on projects in a subtree
  87. .. rest_method:: PUT /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  88.  
  89. Relationship:
  90. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#assign-role-to-group``
  91. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
  92.  
  93. The inherited role assignment is anchored to a project and applied to its
  94. subtree in the projects hierarchy (both existing and future projects).
  95. @@ -258,69 +258,13 @@ Request
  96. - role_id: role_id_path
  97.  
  98.  
  99. -List user's inherited project roles on project
  100. -==============================================
  101. -
  102. -.. rest_method:: GET /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/inherited_to_projects
  103. -
  104. -Relationship:
  105. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-inherited-roles-for-user``
  106. -
  107. -The list only contains those roles assigned to this project that were specified
  108. -as being inherited to its subtree.
  109. -
  110. -Normal response codes: 200
  111. -
  112. -Request
  113. --------
  114. -
  115. -.. rest_parameters:: parameters.yaml
  116. -
  117. - - project_id: project_id_path
  118. - - user_id: user_id_path
  119. -
  120. -Response Example
  121. -----------------
  122. -
  123. -.. literalinclude:: samples/admin/user-roles-list-response.json
  124. - :language: javascript
  125. -
  126. -
  127. -List group's inherited project roles on project
  128. -===============================================
  129. -
  130. -.. rest_method:: GET /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/inherited_to_projects
  131. -
  132. -Relationship:
  133. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-roles-for-group``
  134. -
  135. -The list only contains those roles assigned to this project that were specified
  136. -as being inherited to its subtree.
  137. -
  138. -Normal response codes: 200
  139. -
  140. -Request
  141. --------
  142. -
  143. -.. rest_parameters:: parameters.yaml
  144. -
  145. - - group_id: group_id_path
  146. - - project_id: project_id_path
  147. -
  148. -Response Example
  149. -----------------
  150. -
  151. -.. literalinclude:: samples/admin/group-roles-list-response.json
  152. - :language: javascript
  153. -
  154. -
  155. Check if user has an inherited project role on project
  156. ======================================================
  157.  
  158. .. rest_method:: HEAD /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  159.  
  160. Relationship:
  161. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-role-for-user``
  162. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
  163.  
  164. Checks whether a user has a role assignment with the ``inherited_to_projects`` flag in a project.
  165.  
  166. @@ -342,7 +286,7 @@ Check if group has an inherited project role on project
  167. .. rest_method:: HEAD /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  168.  
  169. Relationship:
  170. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#check-role-for-group``
  171. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
  172.  
  173. Checks whether a group has a role assignment with the ``inherited_to_projects`` flag in a project.
  174.  
  175. @@ -364,7 +308,7 @@ Revoke an inherited project role from user on project
  176. .. rest_method:: DELETE /v3/OS-INHERIT/projects/{project_id}/users/{user_id}/roles/{role_id}/inherited_to_projects
  177.  
  178. Relationship:
  179. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-user``
  180. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_user_role_inherited_to_projects``
  181.  
  182. Normal response codes: 204
  183.  
  184. @@ -384,7 +328,7 @@ Revoke an inherited project role from group on project
  185. .. rest_method:: DELETE /v3/OS-INHERIT/projects/{project_id}/groups/{group_id}/roles/{role_id}/inherited_to_projects
  186.  
  187. Relationship:
  188. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#revoke-role-from-group``
  189. +``http://docs.openstack.org/api/openstack-identity/3/ext/OS-INHERIT/1.0/rel/project_group_role_inherited_to_projects``
  190.  
  191. Normal response codes: 204
  192.  
  193. @@ -404,7 +348,7 @@ List effective role assignments
  194. .. rest_method:: GET /v3/role_assignments
  195.  
  196. Relationship:
  197. -``http://developer.openstack.org/api-ref/identity/v3/?expanded=#list-effective-role-assignments``
  198. +``http://docs.openstack.org/api/openstack-identity/3/rel/role_assignments``
  199.  
  200. Optional query parameters:
  201.  
  202. 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
  203. deleted file mode 100644
  204. index 21861d22b..000000000
  205. --- a/api-ref/source/v3/samples/admin/group-roles-list-response.json
  206. +++ /dev/null
  207. @@ -1,23 +0,0 @@
  208. -{
  209. - "roles": [
  210. - {
  211. - "id": "91011",
  212. - "links": {
  213. - "self": "http://example.com/identity/v3/roles/91011"
  214. - },
  215. - "name": "admin"
  216. - },
  217. - {
  218. - "id": "91011",
  219. - "links": {
  220. - "self": "http://example.com/identity/v3/roles/91011"
  221. - },
  222. - "name": "admin"
  223. - }
  224. - ],
  225. - "links": {
  226. - "self": "http://example.com/identity/v3/OS-INHERIT/projects/1234/groups/5678/roles/inherited_to_projects",
  227. - "previous": null,
  228. - "next": null
  229. - }
  230. -}
  231. 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
  232. deleted file mode 100644
  233. index 9740be6cf..000000000
  234. --- a/api-ref/source/v3/samples/admin/user-roles-list-response.json
  235. +++ /dev/null
  236. @@ -1,23 +0,0 @@
  237. -{
  238. - "roles": [
  239. - {
  240. - "id": "91011",
  241. - "links": {
  242. - "self": "http://example.com/identity/v3/roles/91011"
  243. - },
  244. - "name": "admin"
  245. - },
  246. - {
  247. - "id": "91011",
  248. - "links": {
  249. - "self": "http://example.com/identity/v3/roles/91011"
  250. - },
  251. - "name": "admin"
  252. - }
  253. - ],
  254. - "links": {
  255. - "self": "http://example.com/identity/v3/OS-INHERIT/projects/1234/users/5678/roles/inherited_to_projects",
  256. - "previous": null,
  257. - "next": null
  258. - }
  259. -}
  260. diff --git a/keystone/assignment/core.py b/keystone/assignment/core.py
  261. index 6a6717a35..cd3842d8d 100644
  262. --- a/keystone/assignment/core.py
  263. +++ b/keystone/assignment/core.py
  264. @@ -588,7 +588,7 @@ class Manager(manager.Manager):
  265. indirect['domain_id'] = ref.pop('domain_id')
  266.  
  267. ref['project_id'] = project_id
  268. - ref.pop('inherited_to_projects')
  269. + ref.pop('inherited_to_projects', None)
  270.  
  271. return ref
  272.  
  273. @@ -652,7 +652,12 @@ class Manager(manager.Manager):
  274. ref, user_id, project_id, subtree_ids, expand_groups)
  275. elif 'group_id' in ref and expand_groups:
  276. return expand_group_assignment(ref, user_id)
  277. - return [ref]
  278. + refs = [ref]
  279. + if CONF.projects_hierarchy.enabled:
  280. + LOG.debug("use projects hierarchy refs")
  281. + refs += expand_inherited_assignment(
  282. + ref, user_id, project_id, None, expand_groups)
  283. + return refs
  284.  
  285. def add_implied_roles(self, role_refs):
  286. """Expand out implied roles.
  287. @@ -847,7 +852,19 @@ class Manager(manager.Manager):
  288. role_id=role_id, user_id=user_id, group_ids=group_ids,
  289. inherited_to_projects=True)
  290.  
  291. - return non_inherited_refs + inherited_refs
  292. + result_refs = non_inherited_refs + inherited_refs
  293. + if project_id and CONF.projects_hierarchy.enabled:
  294. + # List inherited assignments from the parent projects
  295. + parents_ids = self.resource_api.list_project_parents_ids(
  296. + project_id)['parents_ids']
  297. + LOG.debug("get refs from parents: %s", parents_ids)
  298. + base_refs = self.driver.list_role_assignments(
  299. + role_id=role_id, user_id=user_id, group_ids=group_ids,
  300. + project_ids=parents_ids,
  301. + inherited_to_projects=inherited)
  302. + result_refs += base_refs
  303. +
  304. + return result_refs
  305.  
  306. # If filtering by group or inherited domain assignment the list is
  307. # guaranteed to be empty
  308. diff --git a/keystone/common/controller.py b/keystone/common/controller.py
  309. index c32c77749..1a349a9e3 100644
  310. --- a/keystone/common/controller.py
  311. +++ b/keystone/common/controller.py
  312. @@ -718,6 +718,14 @@ class V3Controller(wsgi.Application):
  313. _LW('No domain information specified as part of list request'))
  314. raise exception.Unauthorized()
  315.  
  316. + def _get_project_id_from_token(self, request):
  317. + """Get the project_id for a v3 list call."""
  318. + token_ref = utils.get_token_ref(request.context_dict)
  319. + if token_ref.project_scoped:
  320. + return token_ref.project_id
  321. + LOG.warning(_LW('No project information for project aware call'))
  322. + raise exception.Unauthorized()
  323. +
  324. def _get_domain_id_from_token(self, request):
  325. """Get the domain_id for a v3 create call.
  326.  
  327. @@ -799,6 +807,17 @@ class V3Controller(wsgi.Application):
  328. utils.flatten_dict(policy_dict))
  329. LOG.debug('RBAC: Authorization granted')
  330.  
  331. + def check_group_ownership(self, request, ref):
  332. + # we use project_id as domain id, but keep original domain_scope
  333. + if ref['domain_id'] != self._get_project_id_from_token(request):
  334. + raise exception.Forbidden()
  335. +
  336. + def check_user_ownership(self, request, ref):
  337. + # we use project_id as domain id, but keep original domain_scope
  338. + scope_project_id = self._get_project_id_from_token(request)
  339. + if ref.get('default_project_id') != scope_project_id:
  340. + raise exception.Forbidden()
  341. +
  342. @classmethod
  343. def filter_params(cls, ref):
  344. """Remove unspecified parameters from the dictionary.
  345. @@ -814,3 +833,7 @@ class V3Controller(wsgi.Application):
  346. for blocked_param in blocked_keys:
  347. del ref[blocked_param]
  348. return ref
  349. +
  350. + def need_filter_by_projects_hierarchy(self, request):
  351. + """Check that request response should be filtered."""
  352. + return not request.context.is_admin and CONF.projects_hierarchy.enabled
  353. diff --git a/keystone/common/utils.py b/keystone/common/utils.py
  354. index 096e077b5..af093daf9 100644
  355. --- a/keystone/common/utils.py
  356. +++ b/keystone/common/utils.py
  357. @@ -156,7 +156,7 @@ def check_password(password, hashed):
  358. return passlib.hash.sha512_crypt.verify(password_utf8, hashed)
  359.  
  360.  
  361. -def attr_as_boolean(val_attr):
  362. +def attr_as_boolean(val_attr, default=True):
  363. """Return the boolean value, decoded from a string.
  364.  
  365. We test explicitly for a value meaning False, which can be one of
  366. @@ -165,7 +165,7 @@ def attr_as_boolean(val_attr):
  367. meaning True.
  368.  
  369. """
  370. - return strutils.bool_from_string(val_attr, default=True)
  371. + return strutils.bool_from_string(val_attr, default=default)
  372.  
  373.  
  374. def get_blob_from_credential(credential):
  375. diff --git a/keystone/conf/__init__.py b/keystone/conf/__init__.py
  376. index eb08edf45..78468057e 100644
  377. --- a/keystone/conf/__init__.py
  378. +++ b/keystone/conf/__init__.py
  379. @@ -39,6 +39,7 @@ from keystone.conf import oauth1
  380. from keystone.conf import os_inherit
  381. from keystone.conf import paste_deploy
  382. from keystone.conf import policy
  383. +from keystone.conf import projects_hierarchy
  384. from keystone.conf import resource
  385. from keystone.conf import revoke
  386. from keystone.conf import role
  387. @@ -75,6 +76,7 @@ conf_modules = [
  388. os_inherit,
  389. paste_deploy,
  390. policy,
  391. + projects_hierarchy,
  392. resource,
  393. revoke,
  394. role,
  395. diff --git a/keystone/conf/projects_hierarchy.py b/keystone/conf/projects_hierarchy.py
  396. new file mode 100644
  397. index 000000000..ec140014e
  398. --- /dev/null
  399. +++ b/keystone/conf/projects_hierarchy.py
  400. @@ -0,0 +1,37 @@
  401. +# Licensed under the Apache License, Version 2.0 (the "License"); you may
  402. +# not use this file except in compliance with the License. You may obtain
  403. +# a copy of the License at
  404. +#
  405. +# http://www.apache.org/licenses/LICENSE-2.0
  406. +#
  407. +# Unless required by applicable law or agreed to in writing, software
  408. +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  409. +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  410. +# License for the specific language governing permissions and limitations
  411. +# under the License.
  412. +
  413. +from oslo_config import cfg
  414. +
  415. +from keystone.conf import utils
  416. +
  417. +
  418. +enabled = cfg.BoolOpt(
  419. + 'enabled',
  420. + default=False,
  421. + help=utils.fmt("""
  422. +This allows automatic access to child projects within hierarchy.
  423. +"""))
  424. +
  425. +
  426. +GROUP_NAME = __name__.split('.')[-1]
  427. +ALL_OPTS = [
  428. + enabled,
  429. +]
  430. +
  431. +
  432. +def register_opts(conf):
  433. + conf.register_opts(ALL_OPTS, group=GROUP_NAME)
  434. +
  435. +
  436. +def list_opts():
  437. + return {GROUP_NAME: ALL_OPTS}
  438. diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py
  439. index 4f161ee17..4f790e8e9 100644
  440. --- a/keystone/identity/controllers.py
  441. +++ b/keystone/identity/controllers.py
  442. @@ -200,6 +200,10 @@ class UserV3(controller.V3Controller):
  443. ref = {}
  444. ref['user'] = self.identity_api.get_user(user_id)
  445. ref['group'] = self.identity_api.get_group(group_id)
  446. + # allow access for cloud_admin
  447. + if self.need_filter_by_projects_hierarchy(request):
  448. + # check it here because, it is more convenient
  449. + self.check_user_ownership(request, ref['user'])
  450. self.check_protection(request, prep_info, ref)
  451.  
  452. def _check_group_protection(self, request, prep_info, group_id):
  453. @@ -213,6 +217,15 @@ class UserV3(controller.V3Controller):
  454. # The manager layer will generate the unique ID for users
  455. ref = self._normalize_dict(user)
  456. ref = self._normalize_domain_id(request, ref)
  457. + if CONF.projects_hierarchy.enabled:
  458. + LOG.debug("create user %s", ref)
  459. + if request.context.is_admin and not ref.get('default_project_id'):
  460. + raise exception.ValidationError(
  461. + attribute='default_project_id', target=user)
  462. + if not request.context.is_admin:
  463. + scope_project_id = self._get_project_id_from_token(request)
  464. + ref['default_project_id'] = scope_project_id
  465. +
  466. initiator = notifications._get_request_audit_info(request.context_dict)
  467. ref = self.identity_api.create_user(ref, initiator)
  468. return UserV3.wrap_member(request.context_dict, ref)
  469. @@ -220,6 +233,9 @@ class UserV3(controller.V3Controller):
  470. @controller.filterprotected('domain_id', 'enabled', 'name')
  471. def list_users(self, request, filters):
  472. hints = UserV3.build_driver_hints(request, filters)
  473. + if self.need_filter_by_projects_hierarchy(request):
  474. + hints.add_filter(
  475. + 'default_project_id', self._get_project_id_from_token(request))
  476. domain = self._get_domain_id_for_list_request(request)
  477. refs = self.identity_api.list_users(domain_scope=domain, hints=hints)
  478. return UserV3.wrap_collection(request.context_dict, refs, hints=hints)
  479. @@ -229,11 +245,16 @@ class UserV3(controller.V3Controller):
  480. def list_users_in_group(self, request, filters, group_id):
  481. hints = UserV3.build_driver_hints(request, filters)
  482. refs = self.identity_api.list_users_in_group(group_id, hints=hints)
  483. + if self.need_filter_by_projects_hierarchy(request):
  484. + proj = self._get_project_id_from_token(request)
  485. + refs = [x for x in refs if x.get('default_project_id') == proj]
  486. return UserV3.wrap_collection(request.context_dict, refs, hints=hints)
  487.  
  488. @controller.protected()
  489. def get_user(self, request, user_id):
  490. ref = self.identity_api.get_user(user_id)
  491. + if self.need_filter_by_projects_hierarchy(request):
  492. + self.check_user_ownership(request, ref)
  493. return UserV3.wrap_member(request.context_dict, ref)
  494.  
  495. def _update_user(self, context, user_id, user):
  496. @@ -247,6 +268,9 @@ class UserV3(controller.V3Controller):
  497. @controller.protected()
  498. def update_user(self, request, user_id, user):
  499. validation.lazy_validate(schema.user_update, user)
  500. + if self.need_filter_by_projects_hierarchy(request):
  501. + ref = self.identity_api.get_user(user_id)
  502. + self.check_user_ownership(request, ref)
  503. return self._update_user(request.context_dict, user_id, user)
  504.  
  505. @controller.protected(callback=_check_user_and_group_protection)
  506. @@ -265,11 +289,17 @@ class UserV3(controller.V3Controller):
  507.  
  508. @controller.protected()
  509. def delete_user(self, request, user_id):
  510. + if self.need_filter_by_projects_hierarchy(request):
  511. + ref = self.identity_api.get_user(user_id)
  512. + self.check_user_ownership(request, ref)
  513. initiator = notifications._get_request_audit_info(request.context_dict)
  514. return self.identity_api.delete_user(user_id, initiator)
  515.  
  516. @controller.protected()
  517. def change_password(self, request, user_id, user):
  518. + if self.need_filter_by_projects_hierarchy(request):
  519. + ref = self.identity_api.get_user(user_id)
  520. + self.check_user_ownership(request, ref)
  521. original_password = user.get('original_password')
  522. if original_password is None:
  523. raise exception.ValidationError(target='user',
  524. @@ -299,6 +329,8 @@ class GroupV3(controller.V3Controller):
  525. ref = {}
  526. ref['user'] = self.identity_api.get_user(user_id)
  527. self.check_protection(request, prep_info, ref)
  528. + if self.need_filter_by_projects_hierarchy(request):
  529. + self.check_user_ownership(request, ref['user'])
  530.  
  531. @controller.protected()
  532. def create_group(self, request, group):
  533. diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py
  534. index 2523e8dee..0746865a2 100644
  535. --- a/keystone/resource/controllers.py
  536. +++ b/keystone/resource/controllers.py
  537. @@ -15,12 +15,16 @@
  538.  
  539. """Workflow Logic the Resource service."""
  540.  
  541. +import copy
  542. import uuid
  543.  
  544. from six.moves import http_client
  545.  
  546. +from oslo_log import log
  547. +
  548. from keystone.common import controller
  549. from keystone.common import dependency
  550. +from keystone.common import utils
  551. from keystone.common import validation
  552. from keystone.common import wsgi
  553. import keystone.conf
  554. @@ -31,6 +35,7 @@ from keystone.resource import schema
  555.  
  556.  
  557. CONF = keystone.conf.CONF
  558. +LOG = log.getLogger(__name__)
  559.  
  560.  
  561. @dependency.requires('resource_api')
  562. @@ -235,11 +240,21 @@ class ProjectV3(controller.V3Controller):
  563.  
  564. if not ref.get('is_domain'):
  565. ref = self._normalize_domain_id(request, ref)
  566. - # Our API requires that you specify the location in the hierarchy
  567. - # unambiguously. This could be by parent_id or, if it is a top level
  568. - # project, just by providing a domain_id.
  569. - if not ref.get('parent_id'):
  570. - ref['parent_id'] = ref.get('domain_id')
  571. +
  572. + if CONF.projects_hierarchy.enabled:
  573. + if request.context.is_admin and not ref.get('parent_id'):
  574. + raise exception.ValidationError(
  575. + attribute='parent_id', target=ref)
  576. + if not request.context.is_admin:
  577. + token_ref = utils.get_token_ref(request.context_dict)
  578. + ref['parent_id'] = token_ref.project_id
  579. + ref['domain_id'] = token_ref.project_domain_id
  580. + else:
  581. + # Our API requires that you specify the location in the hierarchy
  582. + # unambiguously. This could be by parent_id or,
  583. + # if it is a top level project, just by providing a domain_id.
  584. + if not ref.get('parent_id'):
  585. + ref['parent_id'] = ref.get('domain_id')
  586.  
  587. initiator = notifications._get_request_audit_info(request.context_dict)
  588. try:
  589. @@ -257,7 +272,28 @@ class ProjectV3(controller.V3Controller):
  590. # False (which in query terms means '0'
  591. if 'is_domain' not in request.params:
  592. hints.add_filter('is_domain', '0')
  593. - refs = self.resource_api.list_projects(hints=hints)
  594. +
  595. + search_domains = utils.attr_as_boolean(
  596. + hints.get_exact_filter_by_name('is_domain')['value'],
  597. + default=False
  598. + )
  599. + refs = []
  600. + effective_hints = hints
  601. + if (not search_domains and
  602. + self.need_filter_by_projects_hierarchy(request)):
  603. + token_project_id = self._get_project_id_from_token(request)
  604. + parent_id = hints.get_exact_filter_by_name('parent_id')
  605. + if parent_id and parent_id['value'] != token_project_id:
  606. + # it is prohibit to require children from other scope
  607. + return []
  608. + if not parent_id:
  609. + effective_hints = copy.deepcopy(hints)
  610. + local_hints = copy.deepcopy(hints)
  611. + effective_hints.add_filter('parent_id', token_project_id)
  612. + local_hints.add_filter('id', token_project_id)
  613. + refs = self.resource_api.list_projects(hints=local_hints)
  614. +
  615. + refs += self.resource_api.list_projects(hints=effective_hints)
  616. return ProjectV3.wrap_collection(request.context_dict,
  617. refs, hints=hints)
  618.  
  619. @@ -304,18 +340,35 @@ class ProjectV3(controller.V3Controller):
  620. ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
  621. ref['id'])
  622.  
  623. + def _check_ownership(self, request, project_id, ref=None):
  624. + if self.need_filter_by_projects_hierarchy(request):
  625. + # only direct children and context project itself are accessible.
  626. + if ref is None:
  627. + ref = self.resource_api.get_project(project_id)
  628. + token_project_id = self._get_project_id_from_token(request)
  629. + if token_project_id not in [ref['id'], ref['parent_id']]:
  630. + raise exception.Forbidden()
  631. +
  632. @controller.protected()
  633. def get_project(self, request, project_id):
  634. ref = self.resource_api.get_project(project_id)
  635. + self._check_ownership(request, project_id, ref)
  636. self._expand_project_ref(request, ref)
  637. return ProjectV3.wrap_member(request.context_dict, ref)
  638.  
  639. @controller.protected()
  640. def update_project(self, request, project_id, project):
  641. + self._check_ownership(request, project_id)
  642. validation.lazy_validate(schema.project_update, project)
  643. self._require_matching_id(project_id, project)
  644. self._require_matching_domain_id(
  645. project_id, project, self.resource_api.get_project)
  646. + if CONF.projects_hierarchy.enabled and project.get('parent_id'):
  647. + # in this case parent_id acts like domain_id
  648. + existing_ref = self.resource_api.get_project(project_id)
  649. + if project['parent_id'] != existing_ref['parent_id']:
  650. + raise exception.ValidationError(_('Cannot change Parent ID'))
  651. +
  652. initiator = notifications._get_request_audit_info(request.context_dict)
  653. ref = self.resource_api.update_project(project_id, project,
  654. initiator=initiator)
  655. @@ -323,6 +376,7 @@ class ProjectV3(controller.V3Controller):
  656.  
  657. @controller.protected()
  658. def delete_project(self, request, project_id):
  659. + self._check_ownership(request, project_id)
  660. initiator = notifications._get_request_audit_info(request.context_dict)
  661. return self.resource_api.delete_project(project_id,
  662. initiator=initiator)
  663. diff --git a/keystone/resource/core.py b/keystone/resource/core.py
  664. index abd4e89cc..6bfa96035 100644
  665. --- a/keystone/resource/core.py
  666. +++ b/keystone/resource/core.py
  667. @@ -561,13 +561,25 @@ class Manager(manager.Manager):
  668.  
  669. def list_project_parents(self, project_id, user_id=None):
  670. self._assert_valid_project_id(project_id)
  671. - parents = self.driver.list_project_parents(project_id)
  672. + parents_ids = self.list_project_parents_ids(project_id)['parents_ids']
  673. + parents = self.driver.list_projects_from_ids(parents_ids)
  674. # If a user_id was provided, the returned list should be filtered
  675. # against the projects this user has access to.
  676. if user_id:
  677. parents = self._filter_projects_list(parents, user_id)
  678. return parents
  679.  
  680. + @MEMOIZE
  681. + def list_project_parents_ids(self, project_id):
  682. + if project_id is None:
  683. + msg = _('Project field is required and cannot be empty.')
  684. + raise exception.ValidationError(message=msg)
  685. + return {
  686. + 'parents_ids': [
  687. + x['id'] for x in self.driver.list_project_parents(project_id)
  688. + ]
  689. + }
  690. +
  691. def _build_parents_as_ids_dict(self, project, parents_by_id):
  692. # NOTE(rodrigods): we don't rely in the order of the projects returned
  693. # by the list_project_parents() method. Thus, we create a project cache
  694. diff --git a/keystone/tests/unit/assignment/test_backends.py b/keystone/tests/unit/assignment/test_backends.py
  695. index 019442cfb..cd1b56af5 100644
  696. --- a/keystone/tests/unit/assignment/test_backends.py
  697. +++ b/keystone/tests/unit/assignment/test_backends.py
  698. @@ -1647,6 +1647,74 @@ class AssignmentTests(AssignmentTestHelperMixin):
  699. user_id=self.user_foo['id'],
  700. source_from_group_ids=[group['id']])
  701.  
  702. + def test_list_role_assignments_for_projects_hierarchy(self):
  703. + """Test listing of role assignments retrieved from hierarchy."""
  704. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  705. + root_project = unit.new_project_ref(
  706. + domain_id=CONF.identity.default_domain_id)
  707. + root_project = self.resource_api.create_project(root_project['id'],
  708. + root_project)
  709. + leaf_project = unit.new_project_ref(
  710. + domain_id=CONF.identity.default_domain_id,
  711. + parent_id=root_project['id'])
  712. + leaf_project = self.resource_api.create_project(leaf_project['id'],
  713. + leaf_project)
  714. +
  715. + user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
  716. + user = self.identity_api.create_user(user)
  717. +
  718. + leaf_user = unit.new_user_ref(
  719. + domain_id=CONF.identity.default_domain_id)
  720. + leaf_user = self.identity_api.create_user(leaf_user)
  721. +
  722. + # Grant root project user role
  723. + self.assignment_api.create_grant(user_id=user['id'],
  724. + project_id=root_project['id'],
  725. + role_id=self.role_admin['id'])
  726. + self.assignment_api.create_grant(user_id=user['id'],
  727. + project_id=root_project['id'],
  728. + role_id=self.role_member['id'])
  729. +
  730. + # Grant leaf project user role
  731. + self.assignment_api.create_grant(user_id=leaf_user['id'],
  732. + project_id=leaf_project['id'],
  733. + role_id=self.role_admin['id'])
  734. +
  735. + # Should get back both projects: because the direct role assignment for
  736. + # the root project and inherited role assignment for leaf project
  737. + user_projects = self.assignment_api.list_projects_for_user(user['id'])
  738. + self.assertEqual(2, len(user_projects))
  739. + self.assertIn(root_project, user_projects)
  740. + self.assertIn(leaf_project, user_projects)
  741. +
  742. + leaf_projects = self.assignment_api.list_projects_for_user(
  743. + leaf_user['id'])
  744. + self.assertEqual(1, len(leaf_projects))
  745. + self.assertIn(leaf_project, user_projects)
  746. +
  747. + user_roles = self.assignment_api.get_roles_for_user_and_project(
  748. + user['id'], root_project['id'])
  749. + self.assertEqual(2, len(user_roles))
  750. +
  751. + user_roles = self.assignment_api.get_roles_for_user_and_project(
  752. + user['id'], leaf_project['id'])
  753. + self.assertEqual(2, len(user_roles))
  754. +
  755. + leaf_roles = self.assignment_api.get_roles_for_user_and_project(
  756. + leaf_user['id'], leaf_project['id'])
  757. + self.assertEqual(1, len(leaf_roles))
  758. +
  759. + leaf_roles = self.assignment_api.get_roles_for_user_and_project(
  760. + leaf_user['id'], root_project['id'])
  761. + self.assertEqual(0, len(leaf_roles))
  762. +
  763. + # Disable projects_hiearchy extension
  764. + self.config_fixture.config(group='projects_hierarchy', enabled=False)
  765. + # Should get back just root project - due the direct role assignment
  766. + user_projects = self.assignment_api.list_projects_for_user(user['id'])
  767. + self.assertEqual(1, len(user_projects))
  768. + self.assertIn(root_project, user_projects)
  769. +
  770. def test_add_user_to_project(self):
  771. self.assignment_api.add_user_to_project(self.tenant_baz['id'],
  772. self.user_foo['id'])
  773. diff --git a/keystone/tests/unit/test_v3.py b/keystone/tests/unit/test_v3.py
  774. index 5db673012..19292d4e9 100644
  775. --- a/keystone/tests/unit/test_v3.py
  776. +++ b/keystone/tests/unit/test_v3.py
  777. @@ -226,7 +226,8 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase,
  778. 'name': {'type': 'string'}
  779. },
  780. 'additionalProperties': False
  781. - }
  782. + },
  783. + 'parents_ids': {'type': 'array'}
  784. },
  785. 'additionalProperties': False
  786. }
  787. diff --git a/keystone/tests/unit/test_v3_auth.py b/keystone/tests/unit/test_v3_auth.py
  788. index 7cd31b1bc..56c874777 100644
  789. --- a/keystone/tests/unit/test_v3_auth.py
  790. +++ b/keystone/tests/unit/test_v3_auth.py
  791. @@ -658,6 +658,18 @@ class TokenAPITests(object):
  792. r = self.v3_create_token(auth_data)
  793. self.assertValidProjectScopedTokenResponse(r)
  794.  
  795. + def test_create_project_scoped_token_with_parents_ids(self):
  796. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  797. + auth_data = self.build_authentication_request(
  798. + user_id=self.user['id'],
  799. + password=self.user['password'],
  800. + project_id=self.project['id'])
  801. + pref = self.resource_api.list_project_parents_ids(self.project['id'])
  802. + r = self.v3_create_token(auth_data)
  803. + self.assertValidProjectScopedTokenResponse(r)
  804. + self.assertEqual(
  805. + pref['parents_ids'], r.result['token']['project']['parents_ids'])
  806. +
  807. def test_validate_project_scoped_token(self):
  808. project_scoped_token = self._get_project_scoped_token()
  809. r = self._validate_token(project_scoped_token)
  810. diff --git a/keystone/tests/unit/test_v3_identity.py b/keystone/tests/unit/test_v3_identity.py
  811. index cbfb5aab3..451d33051 100644
  812. --- a/keystone/tests/unit/test_v3_identity.py
  813. +++ b/keystone/tests/unit/test_v3_identity.py
  814. @@ -68,6 +68,78 @@ class IdentityTestCaseStaticAdminToken(test_v3.RestfulTestCase):
  815. self.post('/users', body={'user': ref}, token=CONF.admin_token,
  816. expected_status=http_client.BAD_REQUEST)
  817.  
  818. + def test_projects_hierarchy_create_user_as_admin(self):
  819. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  820. + ref = unit.new_user_ref(domain_id=self.domain_id)
  821. + self.post('/users', body={'user': ref},
  822. + token=CONF.admin_token,
  823. + expected_status=http_client.BAD_REQUEST)
  824. +
  825. + ref['default_project_id'] = self.project_id
  826. + self.post('/users', body={'user': ref}, token=CONF.admin_token)
  827. +
  828. + def test_projects_hierarchy_list_users_as_admin(self):
  829. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  830. + project = unit.new_project_ref(domain_id=self.domain_id)
  831. + self.resource_api.create_project(project['id'], project)
  832. + user = unit.create_user(
  833. + self.identity_api,
  834. + domain_id=self.domain_id, project_id=project['id']
  835. + )
  836. + users = self.get('/users', token=CONF.admin_token).result
  837. + users_ids = [x['id'] for x in users['users']]
  838. + self.assertIn(user['id'], users_ids)
  839. +
  840. + def test_projects_hierarchy_get_user_as_admin(self):
  841. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  842. + project = unit.new_project_ref(domain_id=self.domain_id)
  843. + self.resource_api.create_project(project['id'], project)
  844. + user = unit.create_user(
  845. + self.identity_api,
  846. + domain_id=self.domain_id, project_id=project['id']
  847. + )
  848. + self.get('/users/%s' % user['id'], token=CONF.admin_token)
  849. +
  850. + def test_projects_hierarchy_update_user_as_admin(self):
  851. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  852. + project = unit.new_project_ref(domain_id=self.domain_id)
  853. + self.resource_api.create_project(project['id'], project)
  854. + user = unit.create_user(
  855. + self.identity_api,
  856. + domain_id=self.domain_id, project_id=project['id']
  857. + )
  858. + self.patch('/users/%s' % user['id'],
  859. + body={'user': {'enabled': False}},
  860. + token=CONF.admin_token)
  861. +
  862. + def test_projects_hierarchy_delete_user_as_admin(self):
  863. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  864. + project = unit.new_project_ref(domain_id=self.domain_id)
  865. + self.resource_api.create_project(project['id'], project)
  866. + user = unit.create_user(
  867. + self.identity_api,
  868. + domain_id=self.domain_id, project_id=project['id']
  869. + )
  870. + self.delete('/users/%s' % user['id'], token=CONF.admin_token)
  871. +
  872. + def test_projects_hierarchy_list_users_in_group_as_admin(self):
  873. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  874. + project = unit.new_project_ref(domain_id=self.domain_id)
  875. + self.resource_api.create_project(project['id'], project)
  876. + user = unit.create_user(
  877. + self.identity_api,
  878. + domain_id=self.domain_id, project_id=project['id']
  879. + )
  880. + group = unit.new_group_ref(domain_id=self.domain_id)
  881. + group = self.identity_api.create_group(group)
  882. + self.identity_api.add_user_to_group(user['id'], group['id'])
  883. + self.identity_api.add_user_to_group(self.user_id, group['id'])
  884. + r = self.get('/groups/%s/users' % group['id'],
  885. + token=CONF.admin_token).result
  886. + users_ids = [x['id'] for x in r['users']]
  887. + self.assertIn(user['id'], users_ids)
  888. + self.assertIn(self.user_id, users_ids)
  889. +
  890.  
  891. class IdentityTestCase(test_v3.RestfulTestCase):
  892. """Test users and groups."""
  893. @@ -668,6 +740,125 @@ class IdentityTestCase(test_v3.RestfulTestCase):
  894. token=CONF.admin_token,
  895. expected_status=http_client.BAD_REQUEST)
  896.  
  897. + def test_projects_hierarchy_create_user(self):
  898. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  899. + ref = unit.new_user_ref(domain_id=self.domain_id)
  900. + # it is prohibited to create user without project_scope
  901. + self.post('/users',
  902. + body={'user': ref},
  903. + token=self.get_unscoped_token(),
  904. + expected_status=http_client.FORBIDDEN)
  905. +
  906. + # create user with project scoped token
  907. + user = self.post('/users',
  908. + body={'user': ref},
  909. + token=self.get_scoped_token()).result['user']
  910. + self.assertEqual(self.project_id, user['default_project_id'])
  911. +
  912. + def test_projects_hierarchy_list_users(self):
  913. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  914. + project = unit.new_project_ref(domain_id=self.domain_id)
  915. + self.resource_api.create_project(project['id'], project)
  916. + user = unit.create_user(
  917. + self.identity_api,
  918. + domain_id=self.domain_id, project_id=project['id']
  919. + )
  920. + project_user = unit.create_user(
  921. + self.identity_api,
  922. + domain_id=self.domain_id, project_id=self.project_id
  923. + )
  924. + users = self.get('/users', token=self.get_scoped_token()).result
  925. + users_ids = [x['id'] for x in users['users']]
  926. + self.assertNotIn(user['id'], users_ids)
  927. + self.assertIn(project_user['id'], users_ids)
  928. +
  929. + def test_projects_hierarchy_get_user(self):
  930. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  931. + project = unit.new_project_ref(domain_id=self.domain_id)
  932. + self.resource_api.create_project(project['id'], project)
  933. + user = unit.create_user(
  934. + self.identity_api,
  935. + domain_id=self.domain_id, project_id=project['id']
  936. + )
  937. + project_user = unit.create_user(
  938. + self.identity_api,
  939. + domain_id=self.domain_id, project_id=self.project_id
  940. + )
  941. + self.get('/users/%s' % user['id'],
  942. + token=self.get_scoped_token(),
  943. + expected_status=http_client.FORBIDDEN)
  944. + self.get('/users/%s' % project_user['id'],
  945. + token=self.get_unscoped_token(),
  946. + expected_status=http_client.FORBIDDEN)
  947. + self.get('/users/%s' % project_user['id'],
  948. + token=self.get_scoped_token())
  949. +
  950. + def test_projects_hierarchy_update_user(self):
  951. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  952. + project = unit.new_project_ref(domain_id=self.domain_id)
  953. + self.resource_api.create_project(project['id'], project)
  954. + user = unit.create_user(
  955. + self.identity_api,
  956. + domain_id=self.domain_id, project_id=project['id']
  957. + )
  958. + project_user = unit.create_user(
  959. + self.identity_api,
  960. + domain_id=self.domain_id, project_id=self.project_id
  961. + )
  962. + self.patch('/users/%s' % user['id'],
  963. + body={'user': {'enabled': False}},
  964. + token=self.get_scoped_token(),
  965. + expected_status=http_client.FORBIDDEN)
  966. + self.patch('/users/%s' % project_user['id'],
  967. + body={'user': {'enabled': False}},
  968. + token=self.get_unscoped_token(),
  969. + expected_status=http_client.FORBIDDEN)
  970. + self.patch('/users/%s' % project_user['id'],
  971. + body={'user': {'enabled': False}},
  972. + token=self.get_scoped_token())
  973. +
  974. + def test_projects_hierarchy_delete_user(self):
  975. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  976. + project = unit.new_project_ref(domain_id=self.domain_id)
  977. + self.resource_api.create_project(project['id'], project)
  978. + user = unit.create_user(
  979. + self.identity_api,
  980. + domain_id=self.domain_id, project_id=project['id']
  981. + )
  982. + project_user = unit.create_user(
  983. + self.identity_api,
  984. + domain_id=self.domain_id, project_id=self.project_id
  985. + )
  986. + self.delete('/users/%s' % user['id'],
  987. + token=self.get_scoped_token(),
  988. + expected_status=http_client.FORBIDDEN)
  989. + self.delete('/users/%s' % project_user['id'],
  990. + token=self.get_unscoped_token(),
  991. + expected_status=http_client.FORBIDDEN)
  992. + self.delete('/users/%s' % project_user['id'],
  993. + token=self.get_scoped_token())
  994. +
  995. + def test_projects_hierarchy_list_users_in_group(self):
  996. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  997. + project = unit.new_project_ref(domain_id=self.domain_id)
  998. + self.resource_api.create_project(project['id'], project)
  999. + user = unit.create_user(
  1000. + self.identity_api,
  1001. + domain_id=self.domain_id, project_id=self.project_id
  1002. + )
  1003. + user2 = unit.create_user(
  1004. + self.identity_api,
  1005. + domain_id=self.domain_id, project_id=project['id']
  1006. + )
  1007. + self.identity_api.add_user_to_group(user['id'], self.group_id)
  1008. + self.identity_api.add_user_to_group(user2['id'], self.group_id)
  1009. + self.get('/groups/%s/users' % self.group_id,
  1010. + token=self.get_unscoped_token(),
  1011. + expected_status=http_client.FORBIDDEN)
  1012. + r = self.get('/groups/%s/users' % self.group_id,
  1013. + token=self.get_scoped_token()).result
  1014. + self.assertEqual([user['id']], [x['id'] for x in r['users']])
  1015. +
  1016.  
  1017. class IdentityV3toV2MethodsTestCase(unit.TestCase):
  1018. """Test users V3 to V2 conversion methods."""
  1019. @@ -892,6 +1083,36 @@ class UserSelfServiceChangingPasswordsTestCase(test_v3.RestfulTestCase):
  1020. self.assertNotIn(self.user_ref['password'], log_fix.output)
  1021. self.assertNotIn(new_password, log_fix.output)
  1022.  
  1023. + def test_projects_hierarchy_changing_password(self):
  1024. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1025. +
  1026. + def change_password(user_ref, token, expected_status):
  1027. + update = {
  1028. + 'password': uuid.uuid4().hex,
  1029. + 'original_password': user_ref['password'],
  1030. + }
  1031. + self.post('/users/%s/password' % user_ref['id'],
  1032. + body={'user': update},
  1033. + token=token,
  1034. + expected_status=expected_status)
  1035. +
  1036. + project = unit.new_project_ref(domain_id=self.domain_id)
  1037. + self.resource_api.create_project(project['id'], project)
  1038. + user = unit.create_user(
  1039. + self.identity_api,
  1040. + domain_id=self.domain_id, project_id=project['id']
  1041. + )
  1042. + project_user = unit.create_user(
  1043. + self.identity_api,
  1044. + domain_id=self.domain_id, project_id=self.project_id
  1045. + )
  1046. +
  1047. + change_password(user, self.get_scoped_token(), http_client.FORBIDDEN)
  1048. + change_password(
  1049. + project_user, self.get_unscoped_token(), http_client.FORBIDDEN)
  1050. + change_password(
  1051. + project_user, self.get_scoped_token(), http_client.NO_CONTENT)
  1052. +
  1053.  
  1054. class PasswordValidationTestCase(UserSelfServiceChangingPasswordsTestCase):
  1055. """Test password validation."""
  1056. diff --git a/keystone/tests/unit/test_v3_resource.py b/keystone/tests/unit/test_v3_resource.py
  1057. index bffe54642..2b2e66bfa 100644
  1058. --- a/keystone/tests/unit/test_v3_resource.py
  1059. +++ b/keystone/tests/unit/test_v3_resource.py
  1060. @@ -29,7 +29,35 @@ from keystone.tests.unit import utils as test_utils
  1061. CONF = keystone.conf.CONF
  1062.  
  1063.  
  1064. -class ResourceTestCase(test_v3.RestfulTestCase,
  1065. +class ResourseRestTestCase(test_v3.RestfulTestCase):
  1066. + def _create_projects_hierarchy(self, hierarchy_size=1):
  1067. + """Create a single-branched project hierarchy with the specified size.
  1068. +
  1069. + :param hierarchy_size: the desired hierarchy size, default is 1 -
  1070. + a project with one child.
  1071. +
  1072. + :returns projects: a list of the projects in the created hierarchy.
  1073. +
  1074. + """
  1075. + new_ref = unit.new_project_ref(domain_id=self.domain_id)
  1076. + resp = self.post('/projects', body={'project': new_ref})
  1077. +
  1078. + projects = [resp.result]
  1079. +
  1080. + for i in range(hierarchy_size):
  1081. + new_ref = unit.new_project_ref(
  1082. + domain_id=self.domain_id,
  1083. + parent_id=projects[i]['project']['id'])
  1084. + resp = self.post('/projects',
  1085. + body={'project': new_ref})
  1086. + self.assertValidProjectResponse(resp, new_ref)
  1087. +
  1088. + projects.append(resp.result)
  1089. +
  1090. + return projects
  1091. +
  1092. +
  1093. +class ResourceTestCase(ResourseRestTestCase,
  1094. test_v3.AssignmentTestMixin):
  1095. """Test domains and projects."""
  1096.  
  1097. @@ -686,32 +714,6 @@ class ResourceTestCase(test_v3.RestfulTestCase,
  1098. ref_child['domain_id'] = self.domain['id']
  1099. self.assertValidProjectResponse(r, ref_child)
  1100.  
  1101. - def _create_projects_hierarchy(self, hierarchy_size=1):
  1102. - """Create a single-branched project hierarchy with the specified size.
  1103. -
  1104. - :param hierarchy_size: the desired hierarchy size, default is 1 -
  1105. - a project with one child.
  1106. -
  1107. - :returns projects: a list of the projects in the created hierarchy.
  1108. -
  1109. - """
  1110. - new_ref = unit.new_project_ref(domain_id=self.domain_id)
  1111. - resp = self.post('/projects', body={'project': new_ref})
  1112. -
  1113. - projects = [resp.result]
  1114. -
  1115. - for i in range(hierarchy_size):
  1116. - new_ref = unit.new_project_ref(
  1117. - domain_id=self.domain_id,
  1118. - parent_id=projects[i]['project']['id'])
  1119. - resp = self.post('/projects',
  1120. - body={'project': new_ref})
  1121. - self.assertValidProjectResponse(resp, new_ref)
  1122. -
  1123. - projects.append(resp.result)
  1124. -
  1125. - return projects
  1126. -
  1127. def test_list_projects_filtering_by_parent_id(self):
  1128. """Call ``GET /projects?parent_id={project_id}``."""
  1129. projects = self._create_projects_hierarchy(hierarchy_size=2)
  1130. @@ -1314,6 +1316,172 @@ class ResourceTestCase(test_v3.RestfulTestCase,
  1131. 'project_id': projects[0]['project']['id']},
  1132. expected_status=http_client.FORBIDDEN)
  1133.  
  1134. + def test_projects_hierarchy_create_project(self):
  1135. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1136. + # parent id will be overridden
  1137. + ref = unit.new_project_ref(
  1138. + domain_id=self.domain_id, parent_id='unknown')
  1139. + self.post(
  1140. + '/projects',
  1141. + body={'project': ref},
  1142. + token=self.get_unscoped_token(),
  1143. + expected_status=http_client.FORBIDDEN
  1144. + )
  1145. + r = self.post(
  1146. + '/projects',
  1147. + body={'project': ref},
  1148. + token=self.get_scoped_token()
  1149. + ).result
  1150. + self.assertEqual(self.project_id, r['project']['parent_id'])
  1151. +
  1152. + def test_projects_hierarchy_list_project(self):
  1153. + self._create_projects_hierarchy(1)
  1154. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1155. + ref = unit.new_project_ref(domain_id=self.domain_id)
  1156. + ref = self.post('/projects', body={'project': ref}).result['project']
  1157. + self.get(
  1158. + '/projects',
  1159. + token=self.get_unscoped_token(),
  1160. + expected_status=http_client.FORBIDDEN
  1161. + )
  1162. + r = self.get('/projects', token=self.get_scoped_token()).result
  1163. + actual = [x['id'] for x in r['projects']]
  1164. + expected = [self.project_id, ref['id']]
  1165. + self.assertEqual(sorted(expected), sorted(actual))
  1166. +
  1167. + def test_projects_hierarchy_list_domains(self):
  1168. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1169. + r = self.get(
  1170. + '/projects?is_domain=1',
  1171. + token=self.get_scoped_token(),
  1172. + ).result
  1173. + ids = [x['id'] for x in r['projects']]
  1174. + self.assertIn(self.domain_id, ids)
  1175. +
  1176. + def test_projects_hierarchy_get_project(self):
  1177. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1178. + ref = unit.new_project_ref(domain_id=self.domain_id)
  1179. + ref = self.post('/projects', body={'project': ref}).result['project']
  1180. + self.get(
  1181. + '/projects/%s' % ref['id'],
  1182. + token=self.get_unscoped_token(),
  1183. + expected_status=http_client.FORBIDDEN
  1184. + )
  1185. + self.get('/projects/%s' % ref['id'], token=self.get_scoped_token())
  1186. +
  1187. + def test_projects_hierarchy_update_project(self):
  1188. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1189. + ref = unit.new_project_ref(domain_id=self.domain_id)
  1190. + ref = self.post('/projects', body={'project': ref}).result['project']
  1191. + new_ref = unit.new_project_ref(domain_id=self.domain_id)
  1192. + del new_ref['id']
  1193. + self.patch(
  1194. + '/projects/%s' % ref['id'],
  1195. + body={'project': new_ref},
  1196. + token=self.get_unscoped_token(),
  1197. + expected_status=http_client.FORBIDDEN
  1198. + )
  1199. + self.patch(
  1200. + '/projects/%s' % ref['id'],
  1201. + body={'project': new_ref},
  1202. + token=self.get_scoped_token(),
  1203. + )
  1204. + new_ref['parent_id'] = self.project_id
  1205. + self.patch(
  1206. + '/projects/%s' % ref['id'],
  1207. + body={'project': new_ref},
  1208. + token=self.get_scoped_token(),
  1209. + )
  1210. + new_ref['parent_id'] = self.domain_id
  1211. + self.patch(
  1212. + '/projects/%s' % ref['id'],
  1213. + body={'project': new_ref},
  1214. + token=self.get_scoped_token(),
  1215. + expected_status=http_client.BAD_REQUEST
  1216. + )
  1217. +
  1218. + def test_projects_hierarchy_delete_project(self):
  1219. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1220. + ref = unit.new_project_ref(domain_id=self.domain_id)
  1221. + ref = self.post('/projects', body={'project': ref}).result['project']
  1222. + self.delete(
  1223. + '/projects/%s' % ref['id'],
  1224. + token=self.get_unscoped_token(),
  1225. + expected_status=http_client.FORBIDDEN
  1226. + )
  1227. + self.delete('/projects/%s' % ref['id'], token=self.get_scoped_token())
  1228. +
  1229. +
  1230. +class ResourceV3TestCaseStaticAdminToken(ResourseRestTestCase):
  1231. + EXTENSION_TO_ADD = 'admin_token_auth'
  1232. +
  1233. + def config_overrides(self):
  1234. + super(ResourceV3TestCaseStaticAdminToken, self).config_overrides()
  1235. + self.config_fixture.config(
  1236. + admin_token='ADMIN')
  1237. +
  1238. + def test_projects_hierarchy_create_project_as_admin(self):
  1239. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1240. + # parent id will be overridden
  1241. + ref = unit.new_project_ref(domain_id=self.domain_id)
  1242. + self.post(
  1243. + '/projects',
  1244. + body={'project': ref},
  1245. + token=CONF.admin_token,
  1246. + expected_status=http_client.BAD_REQUEST
  1247. + )
  1248. + ref['parent_id'] = self.project_id
  1249. + r = self.post(
  1250. + '/projects',
  1251. + body={'project': ref},
  1252. + token=CONF.admin_token,
  1253. + ).result
  1254. + self.assertEqual(self.project_id, r['project']['parent_id'])
  1255. +
  1256. + def test_projects_hierarchy_list_project_as_admin(self):
  1257. + refs = self._create_projects_hierarchy(2)
  1258. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1259. + r = self.get('/projects', token=CONF.admin_token).result
  1260. + actual = [x['id'] for x in r['projects']]
  1261. + expected = [x['project']['id'] for x in refs]
  1262. + expected.append(self.project_id)
  1263. + expected.append(self.default_domain_project['id'])
  1264. + self.assertEqual(sorted(expected), sorted(actual))
  1265. +
  1266. + def test_projects_hierarchy_get_project_as_admin(self):
  1267. + refs = self._create_projects_hierarchy(1)
  1268. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1269. + self.get(
  1270. + '/projects/%s' % refs[1]['project']['id'],
  1271. + token=CONF.admin_token,
  1272. + )
  1273. +
  1274. + def test_projects_hierarchy_update_project_as_admin(self):
  1275. + refs = self._create_projects_hierarchy(1)
  1276. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1277. + new_ref = unit.new_project_ref(domain_id=self.domain_id)
  1278. + del new_ref['id']
  1279. + self.patch(
  1280. + '/projects/%s' % refs[1]['project']['id'],
  1281. + body={'project': new_ref},
  1282. + token=CONF.admin_token,
  1283. + )
  1284. + new_ref['parent_id'] = self.domain_id
  1285. + self.patch(
  1286. + '/projects/%s' % refs[1]['project']['id'],
  1287. + body={'project': new_ref},
  1288. + token=CONF.admin_token,
  1289. + expected_status=http_client.BAD_REQUEST
  1290. + )
  1291. +
  1292. + def test_projects_hierarchy_delete_project_as_admin(self):
  1293. + refs = self._create_projects_hierarchy(1)
  1294. + self.config_fixture.config(group='projects_hierarchy', enabled=True)
  1295. + self.delete(
  1296. + '/projects/%s' % refs[1]['project']['id'],
  1297. + token=CONF.admin_token,
  1298. + )
  1299. +
  1300.  
  1301. class ResourceV3toV2MethodsTestCase(unit.TestCase):
  1302. """Test domain V3 to V2 conversion methods."""
  1303. diff --git a/keystone/token/providers/common.py b/keystone/token/providers/common.py
  1304. index 524b9f41c..803029b2b 100644
  1305. --- a/keystone/token/providers/common.py
  1306. +++ b/keystone/token/providers/common.py
  1307. @@ -289,6 +289,10 @@ class V3TokenDataHelper(object):
  1308. else:
  1309. # Projects acting as a domain do not have a domain_id attribute
  1310. filtered_project['domain'] = None
  1311. + if CONF.projects_hierarchy.enabled:
  1312. + pref = self.resource_api.list_project_parents_ids(project_id)
  1313. + filtered_project['parents_ids'] = pref['parents_ids']
  1314. +
  1315. return filtered_project
  1316.  
  1317. def _populate_scope(self, token_data, domain_id, project_id):
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement