OpenStack Identity (Keystone)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

core.py 59KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347
  1. # Copyright 2012 OpenStack Foundation
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. """Main entry point into the Assignment service."""
  15. import copy
  16. import itertools
  17. from oslo_log import log
  18. from keystone.common import cache
  19. from keystone.common import driver_hints
  20. from keystone.common import manager
  21. from keystone.common import provider_api
  22. import keystone.conf
  23. from keystone import exception
  24. from keystone.i18n import _
  25. from keystone import notifications
  26. CONF = keystone.conf.CONF
  27. LOG = log.getLogger(__name__)
  28. PROVIDERS = provider_api.ProviderAPIs
  29. # This is a general cache region for assignment administration (CRUD
  30. # operations).
  31. MEMOIZE = cache.get_memoization_decorator(group='role')
  32. # This builds a discrete cache region dedicated to role assignments computed
  33. # for a given user + project/domain pair. Any write operation to add or remove
  34. # any role assignment should invalidate this entire cache region.
  35. COMPUTED_ASSIGNMENTS_REGION = cache.create_region(name='computed assignments')
  36. MEMOIZE_COMPUTED_ASSIGNMENTS = cache.get_memoization_decorator(
  37. group='role',
  38. region=COMPUTED_ASSIGNMENTS_REGION)
  39. @notifications.listener
  40. class Manager(manager.Manager):
  41. """Default pivot point for the Assignment backend.
  42. See :class:`keystone.common.manager.Manager` for more details on how this
  43. dynamically calls the backend.
  44. """
  45. driver_namespace = 'keystone.assignment'
  46. _provides_api = 'assignment_api'
  47. _SYSTEM_SCOPE_TOKEN = 'system' # nosec
  48. _USER_SYSTEM = 'UserSystem'
  49. _GROUP_SYSTEM = 'GroupSystem'
  50. _PROJECT = 'project'
  51. _ROLE_REMOVED_FROM_USER = 'role_removed_from_user'
  52. _INVALIDATION_USER_PROJECT_TOKENS = 'invalidate_user_project_tokens'
  53. def __init__(self):
  54. assignment_driver = CONF.assignment.driver
  55. super(Manager, self).__init__(assignment_driver)
  56. self.event_callbacks = {
  57. notifications.ACTIONS.deleted: {
  58. 'domain': [self._delete_domain_assignments],
  59. },
  60. }
  61. def _delete_domain_assignments(self, service, resource_type, operations,
  62. payload):
  63. domain_id = payload['resource_info']
  64. self.driver.delete_domain_assignments(domain_id)
  65. def _get_group_ids_for_user_id(self, user_id):
  66. # TODO(morganfainberg): Implement a way to get only group_ids
  67. # instead of the more expensive to_dict() call for each record.
  68. return [x['id'] for
  69. x in PROVIDERS.identity_api.list_groups_for_user(user_id)]
  70. def list_user_ids_for_project(self, project_id):
  71. PROVIDERS.resource_api.get_project(project_id)
  72. assignment_list = self.list_role_assignments(
  73. project_id=project_id, effective=True)
  74. # Use set() to process the list to remove any duplicates
  75. return list(set([x['user_id'] for x in assignment_list]))
  76. def _send_app_cred_notification_for_role_removal(self, role_id):
  77. """Delete all application credential for a specific role.
  78. :param role_id: role identifier
  79. :type role_id: string
  80. """
  81. assignments = self.list_role_assignments(role_id=role_id)
  82. for assignment in assignments:
  83. if 'user_id' in assignment and 'project_id' in assignment:
  84. payload = {
  85. 'user_id': assignment['user_id'],
  86. 'project_id': assignment['project_id']
  87. }
  88. notifications.Audit.internal(
  89. notifications.REMOVE_APP_CREDS_FOR_USER, payload
  90. )
  91. @MEMOIZE_COMPUTED_ASSIGNMENTS
  92. def get_roles_for_user_and_project(self, user_id, project_id):
  93. """Get the roles associated with a user within given project.
  94. This includes roles directly assigned to the user on the
  95. project, as well as those by virtue of group membership or
  96. inheritance.
  97. :returns: a list of role ids.
  98. :raises keystone.exception.ProjectNotFound: If the project doesn't
  99. exist.
  100. """
  101. PROVIDERS.resource_api.get_project(project_id)
  102. assignment_list = self.list_role_assignments(
  103. user_id=user_id, project_id=project_id, effective=True)
  104. # Use set() to process the list to remove any duplicates
  105. return list(set([x['role_id'] for x in assignment_list]))
  106. @MEMOIZE_COMPUTED_ASSIGNMENTS
  107. def get_roles_for_trustor_and_project(self, trustor_id, project_id):
  108. """Get the roles associated with a trustor within given project.
  109. This includes roles directly assigned to the trustor on the
  110. project, as well as those by virtue of group membership or
  111. inheritance, but it doesn't include the domain roles.
  112. :returns: a list of role ids.
  113. :raises keystone.exception.ProjectNotFound: If the project doesn't
  114. exist.
  115. """
  116. PROVIDERS.resource_api.get_project(project_id)
  117. assignment_list = self.list_role_assignments(
  118. user_id=trustor_id, project_id=project_id, effective=True,
  119. strip_domain_roles=False)
  120. # Use set() to process the list to remove any duplicates
  121. return list(set([x['role_id'] for x in assignment_list]))
  122. @MEMOIZE_COMPUTED_ASSIGNMENTS
  123. def get_roles_for_user_and_domain(self, user_id, domain_id):
  124. """Get the roles associated with a user within given domain.
  125. :returns: a list of role ids.
  126. :raises keystone.exception.DomainNotFound: If the domain doesn't exist.
  127. """
  128. PROVIDERS.resource_api.get_domain(domain_id)
  129. assignment_list = self.list_role_assignments(
  130. user_id=user_id, domain_id=domain_id, effective=True)
  131. # Use set() to process the list to remove any duplicates
  132. return list(set([x['role_id'] for x in assignment_list]))
  133. def get_roles_for_groups(self, group_ids, project_id=None, domain_id=None):
  134. """Get a list of roles for this group on domain and/or project."""
  135. # if no group ids were passed, there are no roles. Without this check,
  136. # all assignments for the project or domain will be fetched,
  137. # which is not what we want.
  138. if not group_ids:
  139. return []
  140. if project_id is not None:
  141. PROVIDERS.resource_api.get_project(project_id)
  142. assignment_list = self.list_role_assignments(
  143. source_from_group_ids=group_ids, project_id=project_id,
  144. effective=True)
  145. elif domain_id is not None:
  146. assignment_list = self.list_role_assignments(
  147. source_from_group_ids=group_ids, domain_id=domain_id,
  148. effective=True)
  149. else:
  150. raise AttributeError(_("Must specify either domain or project"))
  151. role_ids = list(set([x['role_id'] for x in assignment_list]))
  152. return PROVIDERS.role_api.list_roles_from_ids(role_ids)
  153. @notifications.role_assignment('created')
  154. def _add_role_to_user_and_project_adapter(self, role_id, user_id=None,
  155. group_id=None, domain_id=None,
  156. project_id=None,
  157. inherited_to_projects=False,
  158. context=None):
  159. # The parameters for this method must match the parameters for
  160. # create_grant so that the notifications.role_assignment decorator
  161. # will work.
  162. PROVIDERS.resource_api.get_project(project_id)
  163. PROVIDERS.role_api.get_role(role_id)
  164. self.driver.add_role_to_user_and_project(user_id, project_id, role_id)
  165. def add_role_to_user_and_project(self, user_id, project_id, role_id):
  166. self._add_role_to_user_and_project_adapter(
  167. role_id, user_id=user_id, project_id=project_id)
  168. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  169. # TODO(henry-nash): We might want to consider list limiting this at some
  170. # point in the future.
  171. @MEMOIZE_COMPUTED_ASSIGNMENTS
  172. def list_projects_for_user(self, user_id):
  173. # FIXME(lbragstad): Without the use of caching, listing effective role
  174. # assignments is slow, especially with large data set (lots of users
  175. # with multiple role assignments). This should serve as a marker in
  176. # case we have the opportunity to come back and optimize this code so
  177. # that it can be performant without having a hard dependency on
  178. # caching. Please see https://bugs.launchpad.net/keystone/+bug/1700852
  179. # for more details.
  180. assignment_list = self.list_role_assignments(
  181. user_id=user_id, effective=True)
  182. # Use set() to process the list to remove any duplicates
  183. project_ids = list(set([x['project_id'] for x in assignment_list
  184. if x.get('project_id')]))
  185. return PROVIDERS.resource_api.list_projects_from_ids(project_ids)
  186. # TODO(henry-nash): We might want to consider list limiting this at some
  187. # point in the future.
  188. @MEMOIZE_COMPUTED_ASSIGNMENTS
  189. def list_domains_for_user(self, user_id):
  190. assignment_list = self.list_role_assignments(
  191. user_id=user_id, effective=True)
  192. # Use set() to process the list to remove any duplicates
  193. domain_ids = list(set([x['domain_id'] for x in assignment_list
  194. if x.get('domain_id')]))
  195. return PROVIDERS.resource_api.list_domains_from_ids(domain_ids)
  196. def list_domains_for_groups(self, group_ids):
  197. assignment_list = self.list_role_assignments(
  198. source_from_group_ids=group_ids, effective=True)
  199. domain_ids = list(set([x['domain_id'] for x in assignment_list
  200. if x.get('domain_id')]))
  201. return PROVIDERS.resource_api.list_domains_from_ids(domain_ids)
  202. def list_projects_for_groups(self, group_ids):
  203. assignment_list = self.list_role_assignments(
  204. source_from_group_ids=group_ids, effective=True)
  205. project_ids = list(set([x['project_id'] for x in assignment_list
  206. if x.get('project_id')]))
  207. return PROVIDERS.resource_api.list_projects_from_ids(project_ids)
  208. @notifications.role_assignment('deleted')
  209. def _remove_role_from_user_and_project_adapter(self, role_id, user_id=None,
  210. group_id=None,
  211. domain_id=None,
  212. project_id=None,
  213. inherited_to_projects=False,
  214. context=None):
  215. # The parameters for this method must match the parameters for
  216. # delete_grant so that the notifications.role_assignment decorator
  217. # will work.
  218. self.driver.remove_role_from_user_and_project(user_id, project_id,
  219. role_id)
  220. payload = {'user_id': user_id, 'project_id': project_id}
  221. notifications.Audit.internal(
  222. notifications.REMOVE_APP_CREDS_FOR_USER,
  223. payload
  224. )
  225. self._invalidate_token_cache(
  226. role_id, group_id, user_id, project_id, domain_id
  227. )
  228. def remove_role_from_user_and_project(self, user_id, project_id, role_id):
  229. self._remove_role_from_user_and_project_adapter(
  230. role_id, user_id=user_id, project_id=project_id)
  231. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  232. def _invalidate_token_cache(self, role_id, group_id, user_id, project_id,
  233. domain_id):
  234. if group_id:
  235. actor_type = 'group'
  236. actor_id = group_id
  237. elif user_id:
  238. actor_type = 'user'
  239. actor_id = user_id
  240. if domain_id:
  241. target_type = 'domain'
  242. target_id = domain_id
  243. elif project_id:
  244. target_type = 'project'
  245. target_id = project_id
  246. reason = (
  247. 'Invalidating the token cache because role %(role_id)s was '
  248. 'removed from %(actor_type)s %(actor_id)s on %(target_type)s '
  249. '%(target_id)s.' %
  250. {'role_id': role_id, 'actor_type': actor_type,
  251. 'actor_id': actor_id, 'target_type': target_type,
  252. 'target_id': target_id}
  253. )
  254. notifications.invalidate_token_cache_notification(reason)
  255. @notifications.role_assignment('created')
  256. def create_grant(self, role_id, user_id=None, group_id=None,
  257. domain_id=None, project_id=None,
  258. inherited_to_projects=False,
  259. initiator=None):
  260. role = PROVIDERS.role_api.get_role(role_id)
  261. if domain_id:
  262. PROVIDERS.resource_api.get_domain(domain_id)
  263. if project_id:
  264. project = PROVIDERS.resource_api.get_project(project_id)
  265. # For domain specific roles, the domain of the project
  266. # and role must match
  267. if role['domain_id'] and project['domain_id'] != role['domain_id']:
  268. raise exception.DomainSpecificRoleMismatch(
  269. role_id=role_id,
  270. project_id=project_id)
  271. self.driver.create_grant(
  272. role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
  273. project_id=project_id, inherited_to_projects=inherited_to_projects
  274. )
  275. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  276. def get_grant(self, role_id, user_id=None, group_id=None,
  277. domain_id=None, project_id=None,
  278. inherited_to_projects=False):
  279. role_ref = PROVIDERS.role_api.get_role(role_id)
  280. if domain_id:
  281. PROVIDERS.resource_api.get_domain(domain_id)
  282. if project_id:
  283. PROVIDERS.resource_api.get_project(project_id)
  284. self.check_grant_role_id(
  285. role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
  286. project_id=project_id, inherited_to_projects=inherited_to_projects
  287. )
  288. return role_ref
  289. def list_grants(self, user_id=None, group_id=None,
  290. domain_id=None, project_id=None,
  291. inherited_to_projects=False):
  292. if domain_id:
  293. PROVIDERS.resource_api.get_domain(domain_id)
  294. if project_id:
  295. PROVIDERS.resource_api.get_project(project_id)
  296. grant_ids = self.list_grant_role_ids(
  297. user_id=user_id, group_id=group_id, domain_id=domain_id,
  298. project_id=project_id, inherited_to_projects=inherited_to_projects
  299. )
  300. return PROVIDERS.role_api.list_roles_from_ids(grant_ids)
  301. @notifications.role_assignment('deleted')
  302. def delete_grant(self, role_id, user_id=None, group_id=None,
  303. domain_id=None, project_id=None,
  304. inherited_to_projects=False,
  305. initiator=None):
  306. # check if role exist before any processing
  307. PROVIDERS.role_api.get_role(role_id)
  308. if group_id is None:
  309. # check if role exists on the user before revoke
  310. self.check_grant_role_id(
  311. role_id, user_id=user_id, group_id=None, domain_id=domain_id,
  312. project_id=project_id,
  313. inherited_to_projects=inherited_to_projects
  314. )
  315. self._invalidate_token_cache(
  316. role_id, group_id, user_id, project_id, domain_id
  317. )
  318. else:
  319. try:
  320. # check if role exists on the group before revoke
  321. self.check_grant_role_id(
  322. role_id, user_id=None, group_id=group_id,
  323. domain_id=domain_id, project_id=project_id,
  324. inherited_to_projects=inherited_to_projects
  325. )
  326. if CONF.token.revoke_by_id:
  327. self._invalidate_token_cache(
  328. role_id, group_id, user_id, project_id, domain_id
  329. )
  330. except exception.GroupNotFound:
  331. LOG.debug('Group %s not found, no tokens to invalidate.',
  332. group_id)
  333. if domain_id:
  334. PROVIDERS.resource_api.get_domain(domain_id)
  335. if project_id:
  336. PROVIDERS.resource_api.get_project(project_id)
  337. self.driver.delete_grant(
  338. role_id, user_id=user_id, group_id=group_id, domain_id=domain_id,
  339. project_id=project_id, inherited_to_projects=inherited_to_projects
  340. )
  341. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  342. # The methods _expand_indirect_assignment, _list_direct_role_assignments
  343. # and _list_effective_role_assignments below are only used on
  344. # list_role_assignments, but they are not in its scope as nested functions
  345. # since it would significantly increase McCabe complexity, that should be
  346. # kept as it is in order to detect unnecessarily complex code, which is not
  347. # this case.
  348. def _expand_indirect_assignment(self, ref, user_id=None, project_id=None,
  349. subtree_ids=None, expand_groups=True):
  350. """Return a list of expanded role assignments.
  351. This methods is called for each discovered assignment that either needs
  352. a group assignment expanded into individual user assignments, or needs
  353. an inherited assignment to be applied to its children.
  354. In all cases, if either user_id and/or project_id is specified, then we
  355. filter the result on those values.
  356. If project_id is specified and subtree_ids is None, then this
  357. indicates that we are only interested in that one project. If
  358. subtree_ids is not None, then this is an indicator that any
  359. inherited assignments need to be expanded down the tree. The
  360. actual subtree_ids don't need to be used as a filter here, since we
  361. already ensured only those assignments that could affect them
  362. were passed to this method.
  363. If expand_groups is True then we expand groups out to a list of
  364. assignments, one for each member of that group.
  365. """
  366. def create_group_assignment(base_ref, user_id):
  367. """Create a group assignment from the provided ref."""
  368. ref = copy.deepcopy(base_ref)
  369. ref['user_id'] = user_id
  370. indirect = ref.setdefault('indirect', {})
  371. indirect['group_id'] = ref.pop('group_id')
  372. return ref
  373. def expand_group_assignment(ref, user_id):
  374. """Expand group role assignment.
  375. For any group role assignment on a target, it is replaced by a list
  376. of role assignments containing one for each user of that group on
  377. that target.
  378. An example of accepted ref is::
  379. {
  380. 'group_id': group_id,
  381. 'project_id': project_id,
  382. 'role_id': role_id
  383. }
  384. Once expanded, it should be returned as a list of entities like the
  385. one below, one for each each user_id in the provided group_id.
  386. ::
  387. {
  388. 'user_id': user_id,
  389. 'project_id': project_id,
  390. 'role_id': role_id,
  391. 'indirect' : {
  392. 'group_id': group_id
  393. }
  394. }
  395. Returned list will be formatted by the Controller, which will
  396. deduce a role assignment came from group membership if it has both
  397. 'user_id' in the main body of the dict and 'group_id' in indirect
  398. subdict.
  399. """
  400. if user_id:
  401. return [create_group_assignment(ref, user_id=user_id)]
  402. # Note(prashkre): Try to get the users in a group,
  403. # if a group wasn't found in the backend, users are set
  404. # as empty list.
  405. try:
  406. users = PROVIDERS.identity_api.list_users_in_group(
  407. ref['group_id'])
  408. except exception.GroupNotFound:
  409. LOG.warning('Group %(group)s was not found but still has role '
  410. 'assignments.', {'group': ref['group_id']})
  411. users = []
  412. return [create_group_assignment(ref, user_id=m['id'])
  413. for m in users]
  414. def expand_inherited_assignment(ref, user_id, project_id, subtree_ids,
  415. expand_groups):
  416. """Expand inherited role assignments.
  417. If expand_groups is True and this is a group role assignment on a
  418. target, replace it by a list of role assignments containing one for
  419. each user of that group, on every project under that target. If
  420. expand_groups is False, then return a group assignment on an
  421. inherited target.
  422. If this is a user role assignment on a specific target (i.e.
  423. project_id is specified, but subtree_ids is None) then simply
  424. format this as a single assignment (since we are effectively
  425. filtering on project_id). If however, project_id is None or
  426. subtree_ids is not None, then replace this one assignment with a
  427. list of role assignments for that user on every project under
  428. that target.
  429. An example of accepted ref is::
  430. {
  431. 'group_id': group_id,
  432. 'project_id': parent_id,
  433. 'role_id': role_id,
  434. 'inherited_to_projects': 'projects'
  435. }
  436. Once expanded, it should be returned as a list of entities like the
  437. one below, one for each each user_id in the provided group_id and
  438. for each subproject_id in the project_id subtree.
  439. ::
  440. {
  441. 'user_id': user_id,
  442. 'project_id': subproject_id,
  443. 'role_id': role_id,
  444. 'indirect' : {
  445. 'group_id': group_id,
  446. 'project_id': parent_id
  447. }
  448. }
  449. Returned list will be formatted by the Controller, which will
  450. deduce a role assignment came from group membership if it has both
  451. 'user_id' in the main body of the dict and 'group_id' in the
  452. 'indirect' subdict, as well as it is possible to deduce if it has
  453. come from inheritance if it contains both a 'project_id' in the
  454. main body of the dict and 'parent_id' in the 'indirect' subdict.
  455. """
  456. def create_inherited_assignment(base_ref, project_id):
  457. """Create a project assignment from the provided ref.
  458. base_ref can either be a project or domain inherited
  459. assignment ref.
  460. """
  461. ref = copy.deepcopy(base_ref)
  462. indirect = ref.setdefault('indirect', {})
  463. if ref.get('project_id'):
  464. indirect['project_id'] = ref.pop('project_id')
  465. else:
  466. indirect['domain_id'] = ref.pop('domain_id')
  467. ref['project_id'] = project_id
  468. ref.pop('inherited_to_projects')
  469. return ref
  470. # Define expanded project list to which to apply this assignment
  471. if project_id:
  472. # Since ref is an inherited assignment and we are filtering by
  473. # project(s), we are only going to apply the assignment to the
  474. # relevant project(s)
  475. project_ids = [project_id]
  476. if subtree_ids:
  477. project_ids += subtree_ids
  478. # If this is a domain inherited assignment, then we know
  479. # that all the project_ids will get this assignment. If
  480. # it's a project inherited assignment, and the assignment
  481. # point is an ancestor of project_id, then we know that
  482. # again all the project_ids will get the assignment. If,
  483. # however, the assignment point is within the subtree,
  484. # then only a partial tree will get the assignment.
  485. resource_api = PROVIDERS.resource_api
  486. if ref.get('project_id'):
  487. if ref['project_id'] in project_ids:
  488. project_ids = (
  489. [x['id'] for x in
  490. resource_api.list_projects_in_subtree(
  491. ref['project_id'])])
  492. elif ref.get('domain_id'):
  493. # A domain inherited assignment, so apply it to all projects
  494. # in this domain
  495. project_ids = (
  496. [x['id'] for x in
  497. PROVIDERS.resource_api.list_projects_in_domain(
  498. ref['domain_id'])])
  499. else:
  500. # It must be a project assignment, so apply it to its subtree
  501. project_ids = (
  502. [x['id'] for x in
  503. PROVIDERS.resource_api.list_projects_in_subtree(
  504. ref['project_id'])])
  505. new_refs = []
  506. if 'group_id' in ref:
  507. if expand_groups:
  508. # Expand role assignment to all group members on any
  509. # inherited target of any of the projects
  510. for ref in expand_group_assignment(ref, user_id):
  511. new_refs += [create_inherited_assignment(ref, proj_id)
  512. for proj_id in project_ids]
  513. else:
  514. # Just place the group assignment on any inherited target
  515. # of any of the projects
  516. new_refs += [create_inherited_assignment(ref, proj_id)
  517. for proj_id in project_ids]
  518. else:
  519. # Expand role assignment for all projects
  520. new_refs += [create_inherited_assignment(ref, proj_id)
  521. for proj_id in project_ids]
  522. return new_refs
  523. if ref.get('inherited_to_projects') == 'projects':
  524. return expand_inherited_assignment(
  525. ref, user_id, project_id, subtree_ids, expand_groups)
  526. elif 'group_id' in ref and expand_groups:
  527. return expand_group_assignment(ref, user_id)
  528. return [ref]
  529. def add_implied_roles(self, role_refs):
  530. """Expand out implied roles.
  531. The role_refs passed in have had all inheritance and group assignments
  532. expanded out. We now need to look at the role_id in each ref and see
  533. if it is a prior role for some implied roles. If it is, then we need to
  534. duplicate that ref, one for each implied role. We store the prior role
  535. in the indirect dict that is part of such a duplicated ref, so that a
  536. caller can determine where the assignment came from.
  537. """
  538. def _make_implied_ref_copy(prior_ref, implied_role_id):
  539. # Create a ref for an implied role from the ref of a prior role,
  540. # setting the new role_id to be the implied role and the indirect
  541. # role_id to be the prior role
  542. implied_ref = copy.deepcopy(prior_ref)
  543. implied_ref['role_id'] = implied_role_id
  544. indirect = implied_ref.setdefault('indirect', {})
  545. indirect['role_id'] = prior_ref['role_id']
  546. return implied_ref
  547. try:
  548. implied_roles_cache = {}
  549. role_refs_to_check = list(role_refs)
  550. ref_results = list(role_refs)
  551. checked_role_refs = list()
  552. while(role_refs_to_check):
  553. next_ref = role_refs_to_check.pop()
  554. checked_role_refs.append(next_ref)
  555. next_role_id = next_ref['role_id']
  556. if next_role_id in implied_roles_cache:
  557. implied_roles = implied_roles_cache[next_role_id]
  558. else:
  559. implied_roles = (
  560. PROVIDERS.role_api.list_implied_roles(next_role_id))
  561. implied_roles_cache[next_role_id] = implied_roles
  562. for implied_role in implied_roles:
  563. implied_ref = (
  564. _make_implied_ref_copy(
  565. next_ref, implied_role['implied_role_id']))
  566. if implied_ref in checked_role_refs:
  567. # Avoid traversing a cycle
  568. continue
  569. else:
  570. ref_results.append(implied_ref)
  571. role_refs_to_check.append(implied_ref)
  572. except exception.NotImplemented:
  573. LOG.error('Role driver does not support implied roles.')
  574. return ref_results
  575. def _filter_by_role_id(self, role_id, ref_results):
  576. # if we arrive here, we need to filer by role_id.
  577. filter_results = []
  578. for ref in ref_results:
  579. if ref['role_id'] == role_id:
  580. filter_results.append(ref)
  581. return filter_results
  582. def _strip_domain_roles(self, role_refs):
  583. """Post process assignment list for domain roles.
  584. Domain roles are only designed to do the job of inferring other roles
  585. and since that has been done before this method is called, we need to
  586. remove any assignments that include a domain role.
  587. """
  588. def _role_is_global(role_id):
  589. ref = PROVIDERS.role_api.get_role(role_id)
  590. return (ref['domain_id'] is None)
  591. filter_results = []
  592. for ref in role_refs:
  593. if _role_is_global(ref['role_id']):
  594. filter_results.append(ref)
  595. return filter_results
  596. def _list_effective_role_assignments(self, role_id, user_id, group_id,
  597. domain_id, project_id, subtree_ids,
  598. inherited, source_from_group_ids,
  599. strip_domain_roles):
  600. """List role assignments in effective mode.
  601. When using effective mode, besides the direct assignments, the indirect
  602. ones that come from grouping or inheritance are retrieved and will then
  603. be expanded.
  604. The resulting list of assignments will be filtered by the provided
  605. parameters. If subtree_ids is not None, then we also want to include
  606. all subtree_ids in the filter as well. Since we are in effective mode,
  607. group can never act as a filter (since group assignments are expanded
  608. into user roles) and domain can only be filter if we want non-inherited
  609. assignments, since domains can't inherit assignments.
  610. The goal of this method is to only ask the driver for those
  611. assignments as could effect the result based on the parameter filters
  612. specified, hence avoiding retrieving a huge list.
  613. """
  614. def list_role_assignments_for_actor(
  615. role_id, inherited, user_id=None, group_ids=None,
  616. project_id=None, subtree_ids=None, domain_id=None):
  617. """List role assignments for actor on target.
  618. List direct and indirect assignments for an actor, optionally
  619. for a given target (i.e. projects or domain).
  620. :param role_id: List for a specific role, can be None meaning all
  621. roles
  622. :param inherited: Indicates whether inherited assignments or only
  623. direct assignments are required. If None, then
  624. both are required.
  625. :param user_id: If not None, list only assignments that affect this
  626. user.
  627. :param group_ids: A list of groups required. Only one of user_id
  628. and group_ids can be specified
  629. :param project_id: If specified, only include those assignments
  630. that affect at least this project, with
  631. additionally any projects specified in
  632. subtree_ids
  633. :param subtree_ids: The list of projects in the subtree. If
  634. specified, also include those assignments that
  635. affect these projects. These projects are
  636. guaranteed to be in the same domain as the
  637. project specified in project_id. subtree_ids
  638. can only be specified if project_id has also
  639. been specified.
  640. :param domain_id: If specified, only include those assignments
  641. that affect this domain - by definition this will
  642. not include any inherited assignments
  643. :returns: List of assignments matching the criteria. Any inherited
  644. or group assignments that could affect the resulting
  645. response are included.
  646. """
  647. project_ids_of_interest = None
  648. if project_id:
  649. if subtree_ids:
  650. project_ids_of_interest = subtree_ids + [project_id]
  651. else:
  652. project_ids_of_interest = [project_id]
  653. # List direct project role assignments
  654. non_inherited_refs = []
  655. if inherited is False or inherited is None:
  656. # Get non inherited assignments
  657. non_inherited_refs = self.driver.list_role_assignments(
  658. role_id=role_id, domain_id=domain_id,
  659. project_ids=project_ids_of_interest, user_id=user_id,
  660. group_ids=group_ids, inherited_to_projects=False)
  661. inherited_refs = []
  662. if inherited is True or inherited is None:
  663. # Get inherited assignments
  664. if project_id:
  665. # The project and any subtree are guaranteed to be owned by
  666. # the same domain, so since we are filtering by these
  667. # specific projects, then we can only get inherited
  668. # assignments from their common domain or from any of
  669. # their parents projects.
  670. # List inherited assignments from the project's domain
  671. proj_domain_id = PROVIDERS.resource_api.get_project(
  672. project_id)['domain_id']
  673. inherited_refs += self.driver.list_role_assignments(
  674. role_id=role_id, domain_id=proj_domain_id,
  675. user_id=user_id, group_ids=group_ids,
  676. inherited_to_projects=True)
  677. # For inherited assignments from projects, since we know
  678. # they are from the same tree the only places these can
  679. # come from are from parents of the main project or
  680. # inherited assignments on the project or subtree itself.
  681. source_ids = [project['id'] for project in
  682. PROVIDERS.resource_api.list_project_parents(
  683. project_id)]
  684. if subtree_ids:
  685. source_ids += project_ids_of_interest
  686. if source_ids:
  687. inherited_refs += self.driver.list_role_assignments(
  688. role_id=role_id, project_ids=source_ids,
  689. user_id=user_id, group_ids=group_ids,
  690. inherited_to_projects=True)
  691. else:
  692. # List inherited assignments without filtering by target
  693. inherited_refs = self.driver.list_role_assignments(
  694. role_id=role_id, user_id=user_id, group_ids=group_ids,
  695. inherited_to_projects=True)
  696. return non_inherited_refs + inherited_refs
  697. # If filtering by group or inherited domain assignment the list is
  698. # guaranteed to be empty
  699. if group_id or (domain_id and inherited):
  700. return []
  701. if user_id and source_from_group_ids:
  702. # You can't do both - and since source_from_group_ids is only used
  703. # internally, this must be a coding error by the caller.
  704. msg = _('Cannot list assignments sourced from groups and filtered '
  705. 'by user ID.')
  706. raise exception.UnexpectedError(msg)
  707. # If filtering by domain, then only non-inherited assignments are
  708. # relevant, since domains don't inherit assignments
  709. inherited = False if domain_id else inherited
  710. # List user or explicit group assignments.
  711. # Due to the need to expand implied roles, this call will skip
  712. # filtering by role_id and instead return the whole set of roles.
  713. # Matching on the specified role is performed at the end.
  714. direct_refs = list_role_assignments_for_actor(
  715. role_id=None, user_id=user_id, group_ids=source_from_group_ids,
  716. project_id=project_id, subtree_ids=subtree_ids,
  717. domain_id=domain_id, inherited=inherited)
  718. # And those from the user's groups, so long as we are not restricting
  719. # to a set of source groups (in which case we already got those
  720. # assignments in the direct listing above).
  721. group_refs = []
  722. if not source_from_group_ids and user_id:
  723. group_ids = self._get_group_ids_for_user_id(user_id)
  724. if group_ids:
  725. group_refs = list_role_assignments_for_actor(
  726. role_id=None, project_id=project_id,
  727. subtree_ids=subtree_ids, group_ids=group_ids,
  728. domain_id=domain_id, inherited=inherited)
  729. # Expand grouping and inheritance on retrieved role assignments
  730. refs = []
  731. expand_groups = (source_from_group_ids is None)
  732. for ref in (direct_refs + group_refs):
  733. refs += self._expand_indirect_assignment(
  734. ref, user_id, project_id, subtree_ids, expand_groups)
  735. refs = self.add_implied_roles(refs)
  736. if strip_domain_roles:
  737. refs = self._strip_domain_roles(refs)
  738. if role_id:
  739. refs = self._filter_by_role_id(role_id, refs)
  740. return refs
  741. def _list_direct_role_assignments(self, role_id, user_id, group_id, system,
  742. domain_id, project_id, subtree_ids,
  743. inherited):
  744. """List role assignments without applying expansion.
  745. Returns a list of direct role assignments, where their attributes match
  746. the provided filters. If subtree_ids is not None, then we also want to
  747. include all subtree_ids in the filter as well.
  748. """
  749. group_ids = [group_id] if group_id else None
  750. project_ids_of_interest = None
  751. if project_id:
  752. if subtree_ids:
  753. project_ids_of_interest = subtree_ids + [project_id]
  754. else:
  755. project_ids_of_interest = [project_id]
  756. project_and_domain_assignments = []
  757. if not system:
  758. project_and_domain_assignments = self.driver.list_role_assignments(
  759. role_id=role_id, user_id=user_id, group_ids=group_ids,
  760. domain_id=domain_id, project_ids=project_ids_of_interest,
  761. inherited_to_projects=inherited)
  762. system_assignments = []
  763. if system or (not project_id and not domain_id and not system):
  764. if user_id:
  765. assignments = self.list_system_grants_for_user(user_id)
  766. for assignment in assignments:
  767. system_assignments.append(
  768. {'system': {'all': True},
  769. 'user_id': user_id,
  770. 'role_id': assignment['id']}
  771. )
  772. elif group_id:
  773. assignments = self.list_system_grants_for_group(group_id)
  774. for assignment in assignments:
  775. system_assignments.append(
  776. {'system': {'all': True},
  777. 'group_id': group_id,
  778. 'role_id': assignment['id']}
  779. )
  780. else:
  781. assignments = self.list_all_system_grants()
  782. for assignment in assignments:
  783. a = {}
  784. if assignment['type'] == self._GROUP_SYSTEM:
  785. a['group_id'] = assignment['actor_id']
  786. elif assignment['type'] == self._USER_SYSTEM:
  787. a['user_id'] = assignment['actor_id']
  788. a['role_id'] = assignment['role_id']
  789. a['system'] = {'all': True}
  790. system_assignments.append(a)
  791. for i, assignment in enumerate(system_assignments):
  792. if role_id and role_id != assignment['role_id']:
  793. system_assignments.pop(i)
  794. assignments = []
  795. for assignment in itertools.chain(
  796. project_and_domain_assignments, system_assignments):
  797. assignments.append(assignment)
  798. return assignments
  799. def list_role_assignments(self, role_id=None, user_id=None, group_id=None,
  800. system=None, domain_id=None, project_id=None,
  801. include_subtree=False, inherited=None,
  802. effective=None, include_names=False,
  803. source_from_group_ids=None,
  804. strip_domain_roles=True):
  805. """List role assignments, honoring effective mode and provided filters.
  806. Returns a list of role assignments, where their attributes match the
  807. provided filters (role_id, user_id, group_id, domain_id, project_id and
  808. inherited). If include_subtree is True, then assignments on all
  809. descendants of the project specified by project_id are also included.
  810. The inherited filter defaults to None, meaning to get both
  811. non-inherited and inherited role assignments.
  812. If effective mode is specified, this means that rather than simply
  813. return the assignments that match the filters, any group or
  814. inheritance assignments will be expanded. Group assignments will
  815. become assignments for all the users in that group, and inherited
  816. assignments will be shown on the projects below the assignment point.
  817. Think of effective mode as being the list of assignments that actually
  818. affect a user, for example the roles that would be placed in a token.
  819. If include_names is set to true the entities' names are returned
  820. in addition to their id's.
  821. source_from_group_ids is a list of group IDs and, if specified, then
  822. only those assignments that are derived from membership of these groups
  823. are considered, and any such assignments will not be expanded into
  824. their user membership assignments. This is different to a group filter
  825. of the resulting list, instead being a restriction on which assignments
  826. should be considered before expansion of inheritance. This option is
  827. only used internally (i.e. it is not exposed at the API level) and is
  828. only supported in effective mode (since in regular mode there is no
  829. difference between this and a group filter, other than it is a list of
  830. groups).
  831. In effective mode, any domain specific roles are usually stripped from
  832. the returned assignments (since such roles are not placed in tokens).
  833. This stripping can be disabled by specifying strip_domain_roles=False,
  834. which is useful for internal calls like trusts which need to examine
  835. the full set of roles.
  836. """
  837. subtree_ids = None
  838. if project_id and include_subtree:
  839. subtree_ids = (
  840. [x['id'] for x in
  841. PROVIDERS.resource_api.list_projects_in_subtree(
  842. project_id)])
  843. if system != 'all':
  844. system = None
  845. if effective:
  846. role_assignments = self._list_effective_role_assignments(
  847. role_id, user_id, group_id, domain_id, project_id,
  848. subtree_ids, inherited, source_from_group_ids,
  849. strip_domain_roles)
  850. else:
  851. role_assignments = self._list_direct_role_assignments(
  852. role_id, user_id, group_id, system, domain_id, project_id,
  853. subtree_ids, inherited)
  854. if include_names:
  855. return self._get_names_from_role_assignments(role_assignments)
  856. return role_assignments
  857. def _get_names_from_role_assignments(self, role_assignments):
  858. role_assign_list = []
  859. for role_asgmt in role_assignments:
  860. new_assign = copy.deepcopy(role_asgmt)
  861. for key, value in role_asgmt.items():
  862. if key == 'domain_id':
  863. _domain = PROVIDERS.resource_api.get_domain(value)
  864. new_assign['domain_name'] = _domain['name']
  865. elif key == 'user_id':
  866. try:
  867. # Note(knikolla): Try to get the user, otherwise
  868. # if the user wasn't found in the backend
  869. # use empty values.
  870. _user = PROVIDERS.identity_api.get_user(value)
  871. except exception.UserNotFound:
  872. msg = ('User %(user)s not found in the'
  873. ' backend but still has role assignments.')
  874. LOG.warning(msg, {'user': value})
  875. new_assign['user_name'] = ''
  876. new_assign['user_domain_id'] = ''
  877. new_assign['user_domain_name'] = ''
  878. else:
  879. new_assign['user_name'] = _user['name']
  880. new_assign['user_domain_id'] = _user['domain_id']
  881. new_assign['user_domain_name'] = (
  882. PROVIDERS.resource_api.get_domain(
  883. _user['domain_id'])['name'])
  884. elif key == 'group_id':
  885. try:
  886. # Note(knikolla): Try to get the group, otherwise
  887. # if the group wasn't found in the backend
  888. # use empty values.
  889. _group = PROVIDERS.identity_api.get_group(value)
  890. except exception.GroupNotFound:
  891. msg = ('Group %(group)s not found in the'
  892. ' backend but still has role assignments.')
  893. LOG.warning(msg, {'group': value})
  894. new_assign['group_name'] = ''
  895. new_assign['group_domain_id'] = ''
  896. new_assign['group_domain_name'] = ''
  897. else:
  898. new_assign['group_name'] = _group['name']
  899. new_assign['group_domain_id'] = _group['domain_id']
  900. new_assign['group_domain_name'] = (
  901. PROVIDERS.resource_api.get_domain(
  902. _group['domain_id'])['name'])
  903. elif key == 'project_id':
  904. _project = PROVIDERS.resource_api.get_project(value)
  905. new_assign['project_name'] = _project['name']
  906. new_assign['project_domain_id'] = _project['domain_id']
  907. new_assign['project_domain_name'] = (
  908. PROVIDERS.resource_api.get_domain(
  909. _project['domain_id'])['name'])
  910. elif key == 'role_id':
  911. _role = PROVIDERS.role_api.get_role(value)
  912. new_assign['role_name'] = _role['name']
  913. if _role['domain_id'] is not None:
  914. new_assign['role_domain_id'] = _role['domain_id']
  915. new_assign['role_domain_name'] = (
  916. PROVIDERS.resource_api.get_domain(
  917. _role['domain_id'])['name'])
  918. role_assign_list.append(new_assign)
  919. return role_assign_list
  920. def delete_group_assignments(self, group_id):
  921. # FIXME(lbragstad): This should be refactored in the Rocky release so
  922. # that we can pass the group_id to the system assignment backend like
  923. # we do with the project and domain assignment backend. Holding off on
  924. # this because it will require an interface change to the backend,
  925. # making it harder to backport for Queens RC.
  926. self.driver.delete_group_assignments(group_id)
  927. system_assignments = self.list_system_grants_for_group(group_id)
  928. for assignment in system_assignments:
  929. self.delete_system_grant_for_group(group_id, assignment['id'])
  930. def delete_user_assignments(self, user_id):
  931. # FIXME(lbragstad): This should be refactored in the Rocky release so
  932. # that we can pass the user_id to the system assignment backend like we
  933. # do with the project and domain assignment backend. Holding off on
  934. # this because it will require an interface change to the backend,
  935. # making it harder to backport for Queens RC.
  936. self.driver.delete_user_assignments(user_id)
  937. system_assignments = self.list_system_grants_for_user(user_id)
  938. for assignment in system_assignments:
  939. self.delete_system_grant_for_user(user_id, assignment['id'])
  940. def check_system_grant_for_user(self, user_id, role_id):
  941. """Check if a user has a specific role on the system.
  942. :param user_id: the ID of the user in the assignment
  943. :param role_id: the ID of the system role in the assignment
  944. :raises keystone.exception.RoleAssignmentNotFound: if the user doesn't
  945. have a role assignment matching the role_id on the system
  946. """
  947. target_id = self._SYSTEM_SCOPE_TOKEN
  948. inherited = False
  949. return self.driver.check_system_grant(
  950. role_id, user_id, target_id, inherited
  951. )
  952. def list_system_grants_for_user(self, user_id):
  953. """Return a list of roles the user has on the system.
  954. :param user_id: the ID of the user
  955. :returns: a list of role assignments the user has system-wide
  956. """
  957. target_id = self._SYSTEM_SCOPE_TOKEN
  958. assignment_type = self._USER_SYSTEM
  959. grants = self.driver.list_system_grants(
  960. user_id, target_id, assignment_type
  961. )
  962. grant_ids = []
  963. for grant in grants:
  964. grant_ids.append(grant['role_id'])
  965. return PROVIDERS.role_api.list_roles_from_ids(grant_ids)
  966. def create_system_grant_for_user(self, user_id, role_id):
  967. """Grant a user a role on the system.
  968. :param user_id: the ID of the user
  969. :param role_id: the ID of the role to grant on the system
  970. """
  971. role = PROVIDERS.role_api.get_role(role_id)
  972. if role.get('domain_id'):
  973. raise exception.ValidationError(
  974. 'Role %(role_id)s is a domain-specific role. Unable to use '
  975. 'a domain-specific role in a system assignment.' % {
  976. 'role_id': role_id
  977. }
  978. )
  979. target_id = self._SYSTEM_SCOPE_TOKEN
  980. assignment_type = self._USER_SYSTEM
  981. inherited = False
  982. self.driver.create_system_grant(
  983. role_id, user_id, target_id, assignment_type, inherited
  984. )
  985. def delete_system_grant_for_user(self, user_id, role_id):
  986. """Remove a system grant from a user.
  987. :param user_id: the ID of the user
  988. :param role_id: the ID of the role to remove from the user on the
  989. system
  990. :raises keystone.exception.RoleAssignmentNotFound: if the user doesn't
  991. have a role assignment with role_id on the system
  992. """
  993. target_id = self._SYSTEM_SCOPE_TOKEN
  994. inherited = False
  995. self.driver.delete_system_grant(role_id, user_id, target_id, inherited)
  996. def check_system_grant_for_group(self, group_id, role_id):
  997. """Check if a group has a specific role on the system.
  998. :param group_id: the ID of the group in the assignment
  999. :param role_id: the ID of the system role in the assignment
  1000. :raises keystone.exception.RoleAssignmentNotFound: if the group doesn't
  1001. have a role assignment matching the role_id on the system
  1002. """
  1003. target_id = self._SYSTEM_SCOPE_TOKEN
  1004. inherited = False
  1005. return self.driver.check_system_grant(
  1006. role_id, group_id, target_id, inherited
  1007. )
  1008. def list_system_grants_for_group(self, group_id):
  1009. """Return a list of roles the group has on the system.
  1010. :param group_id: the ID of the group
  1011. :returns: a list of role assignments the group has system-wide
  1012. """
  1013. target_id = self._SYSTEM_SCOPE_TOKEN
  1014. assignment_type = self._GROUP_SYSTEM
  1015. grants = self.driver.list_system_grants(
  1016. group_id, target_id, assignment_type
  1017. )
  1018. grant_ids = []
  1019. for grant in grants:
  1020. grant_ids.append(grant['role_id'])
  1021. return PROVIDERS.role_api.list_roles_from_ids(grant_ids)
  1022. def create_system_grant_for_group(self, group_id, role_id):
  1023. """Grant a group a role on the system.
  1024. :param group_id: the ID of the group
  1025. :param role_id: the ID of the role to grant on the system
  1026. """
  1027. role = PROVIDERS.role_api.get_role(role_id)
  1028. if role.get('domain_id'):
  1029. raise exception.ValidationError(
  1030. 'Role %(role_id)s is a domain-specific role. Unable to use '
  1031. 'a domain-specific role in a system assignment.' % {
  1032. 'role_id': role_id
  1033. }
  1034. )
  1035. target_id = self._SYSTEM_SCOPE_TOKEN
  1036. assignment_type = self._GROUP_SYSTEM
  1037. inherited = False
  1038. self.driver.create_system_grant(
  1039. role_id, group_id, target_id, assignment_type, inherited
  1040. )
  1041. def delete_system_grant_for_group(self, group_id, role_id):
  1042. """Remove a system grant from a group.
  1043. :param group_id: the ID of the group
  1044. :param role_id: the ID of the role to remove from the group on the
  1045. system
  1046. :raises keystone.exception.RoleAssignmentNotFound: if the group doesn't
  1047. have a role assignment with role_id on the system
  1048. """
  1049. target_id = self._SYSTEM_SCOPE_TOKEN
  1050. inherited = False
  1051. self.driver.delete_system_grant(
  1052. role_id, group_id, target_id, inherited
  1053. )
  1054. def list_all_system_grants(self):
  1055. """Return a list of all system grants."""
  1056. actor_id = None
  1057. target_id = self._SYSTEM_SCOPE_TOKEN
  1058. assignment_type = None
  1059. return self.driver.list_system_grants(
  1060. actor_id, target_id, assignment_type
  1061. )
  1062. class RoleManager(manager.Manager):
  1063. """Default pivot point for the Role backend."""
  1064. driver_namespace = 'keystone.role'
  1065. _provides_api = 'role_api'
  1066. _ROLE = 'role'
  1067. def __init__(self):
  1068. # If there is a specific driver specified for role, then use it.
  1069. # Otherwise retrieve the driver type from the assignment driver.
  1070. role_driver = CONF.role.driver
  1071. if role_driver is None:
  1072. # Explicitly load the assignment manager object
  1073. assignment_driver = CONF.assignment.driver
  1074. assignment_manager_obj = manager.load_driver(
  1075. Manager.driver_namespace,
  1076. assignment_driver)
  1077. role_driver = assignment_manager_obj.default_role_driver()
  1078. super(RoleManager, self).__init__(role_driver)
  1079. @MEMOIZE
  1080. def get_role(self, role_id):
  1081. return self.driver.get_role(role_id)
  1082. def get_unique_role_by_name(self, role_name, hints=None):
  1083. if not hints:
  1084. hints = driver_hints.Hints()
  1085. hints.add_filter("name", role_name, case_sensitive=True)
  1086. found_roles = PROVIDERS.role_api.list_roles(hints)
  1087. if not found_roles:
  1088. raise exception.RoleNotFound(
  1089. _("Role %s is not defined") % role_name
  1090. )
  1091. elif len(found_roles) == 1:
  1092. return {'id': found_roles[0]['id']}
  1093. else:
  1094. raise exception.AmbiguityError(resource='role',
  1095. name=role_name)
  1096. def create_role(self, role_id, role, initiator=None):
  1097. ret = self.driver.create_role(role_id, role)
  1098. notifications.Audit.created(self._ROLE, role_id, initiator)
  1099. if MEMOIZE.should_cache(ret):
  1100. self.get_role.set(ret, self, role_id)
  1101. return ret
  1102. @manager.response_truncated
  1103. def list_roles(self, hints=None):
  1104. return self.driver.list_roles(hints or driver_hints.Hints())
  1105. def update_role(self, role_id, role, initiator=None):
  1106. original_role = self.driver.get_role(role_id)
  1107. if ('domain_id' in role and
  1108. role['domain_id'] != original_role['domain_id']):
  1109. raise exception.ValidationError(
  1110. message=_('Update of `domain_id` is not allowed.'))
  1111. ret = self.driver.update_role(role_id, role)
  1112. notifications.Audit.updated(self._ROLE, role_id, initiator)
  1113. self.get_role.invalidate(self, role_id)
  1114. return ret
  1115. def delete_role(self, role_id, initiator=None):
  1116. PROVIDERS.assignment_api.delete_role_assignments(role_id)
  1117. PROVIDERS.assignment_api._send_app_cred_notification_for_role_removal(
  1118. role_id
  1119. )
  1120. self.driver.delete_role(role_id)
  1121. notifications.Audit.deleted(self._ROLE, role_id, initiator)
  1122. self.get_role.invalidate(self, role_id)
  1123. reason = (
  1124. 'Invalidating the token cache because role %(role_id)s has been '
  1125. 'removed. Role assignments for users will be recalculated and '
  1126. 'enforced accordingly the next time they authenticate or validate '
  1127. 'a token' % {'role_id': role_id}
  1128. )
  1129. notifications.invalidate_token_cache_notification(reason)
  1130. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  1131. # TODO(ayoung): Add notification
  1132. def create_implied_role(self, prior_role_id, implied_role_id):
  1133. implied_role = self.driver.get_role(implied_role_id)
  1134. prior_role = self.driver.get_role(prior_role_id)
  1135. if implied_role['name'] in CONF.assignment.prohibited_implied_role:
  1136. raise exception.InvalidImpliedRole(role_id=implied_role_id)
  1137. if prior_role['domain_id'] is None and implied_role['domain_id']:
  1138. msg = _('Global role cannot imply a domain-specific role')
  1139. raise exception.InvalidImpliedRole(msg,
  1140. role_id=implied_role_id)
  1141. response = self.driver.create_implied_role(
  1142. prior_role_id, implied_role_id)
  1143. COMPUTED_ASSIGNMENTS_REGION.invalidate()
  1144. return response
  1145. def delete_implied_role(self, prior_role_id, implied_role_id):
  1146. self.driver.delete_implied_role(prior_role_id, implied_role_id)
  1147. COMPUTED_ASSIGNMENTS_REGION.invalidate()