1# Copyright 2011 OpenStack Foundation
2# Copyright 2011 Nebula, Inc.
3# All Rights Reserved.
4#
5#    Licensed under the Apache License, Version 2.0 (the "License"); you may
6#    not use this file except in compliance with the License. You may obtain
7#    a copy of the License at
8#
9#         http://www.apache.org/licenses/LICENSE-2.0
10#
11#    Unless required by applicable law or agreed to in writing, software
12#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14#    License for the specific language governing permissions and limitations
15#    under the License.
16
17from debtcollector import removals
18
19from keystoneclient import base
20from keystoneclient import exceptions
21from keystoneclient.i18n import _
22
23
24class Role(base.Resource):
25    """Represents an Identity role.
26
27    Attributes:
28        * id: a uuid that identifies the role
29        * name: user-facing identifier
30        * domain: optional domain for the role
31
32    """
33
34    pass
35
36
37class InferenceRule(base.Resource):
38    """Represents a rule that states one role implies another.
39
40    Attributes:
41        * prior_role: this role implies the other
42        * implied_role: this role is implied by the other
43
44    """
45
46    pass
47
48
49class RoleManager(base.CrudManager):
50    """Manager class for manipulating Identity roles."""
51
52    resource_class = Role
53    collection_key = 'roles'
54    key = 'role'
55    deprecation_msg = 'keystoneclient.v3.roles.InferenceRuleManager'
56
57    def _role_grants_base_url(self, user, group, system, domain, project,
58                              use_inherit_extension):
59        # When called, we have already checked that only one of user & group
60        # and one of domain & project have been specified
61        params = {}
62
63        if project:
64            params['project_id'] = base.getid(project)
65            base_url = '/projects/%(project_id)s'
66        elif domain:
67            params['domain_id'] = base.getid(domain)
68            base_url = '/domains/%(domain_id)s'
69        elif system:
70            if system == 'all':
71                base_url = '/system'
72            else:
73                # NOTE(lbragstad): If we've made it this far, a user is
74                # attempting to do something with system scope that isn't
75                # supported yet (e.g. 'all' is currently the only supported
76                # system scope). In the future that may change but until then
77                # we should fail like we would if a user provided a bogus
78                # project name or domain ID.
79                msg = _("Only a system scope of 'all' is currently supported")
80                raise exceptions.ValidationError(msg)
81
82        if use_inherit_extension:
83            base_url = '/OS-INHERIT' + base_url
84
85        if user:
86            params['user_id'] = base.getid(user)
87            base_url += '/users/%(user_id)s'
88        elif group:
89            params['group_id'] = base.getid(group)
90            base_url += '/groups/%(group_id)s'
91
92        return base_url % params
93
94    def _enforce_mutually_exclusive_group(self, system, domain, project):
95        if not system:
96            if domain and project:
97                msg = _('Specify either a domain or project, not both')
98                raise exceptions.ValidationError(msg)
99            elif not (domain or project):
100                msg = _('Must specify either system, domain, or project')
101                raise exceptions.ValidationError(msg)
102        elif system:
103            if domain and project:
104                msg = _(
105                    'Specify either system, domain, or project, not all three.'
106                )
107                raise exceptions.ValidationError(msg)
108            if domain:
109                msg = _('Specify either system or a domain, not both')
110                raise exceptions.ValidationError(msg)
111            if project:
112                msg = _('Specify either a system or project, not both')
113                raise exceptions.ValidationError(msg)
114
115    def _require_user_xor_group(self, user, group):
116        if user and group:
117            msg = _('Specify either a user or group, not both')
118            raise exceptions.ValidationError(msg)
119        elif not (user or group):
120            msg = _('Must specify either a user or group')
121            raise exceptions.ValidationError(msg)
122
123    def create(self, name, domain=None, **kwargs):
124        """Create a role.
125
126        :param str name: the name of the role.
127        :param domain: the domain of the role. If a value is passed it is a
128                       domain-scoped role, otherwise it's a global role.
129        :type domain: str or :class:`keystoneclient.v3.domains.Domain`
130        :param kwargs: any other attribute provided will be passed to the
131                       server.
132
133        :returns: the created role returned from server.
134        :rtype: :class:`keystoneclient.v3.roles.Role`
135
136        """
137        domain_id = None
138        if domain:
139            domain_id = base.getid(domain)
140
141        return super(RoleManager, self).create(
142            name=name,
143            domain_id=domain_id,
144            **kwargs)
145
146    def get(self, role):
147        """Retrieve a role.
148
149        :param role: the role to be retrieved from the server.
150        :type role: str or :class:`keystoneclient.v3.roles.Role`
151
152        :returns: the specified role returned from server.
153        :rtype: :class:`keystoneclient.v3.roles.Role`
154
155        """
156        return super(RoleManager, self).get(role_id=base.getid(role))
157
158    def list(self, user=None, group=None, system=None, domain=None,
159             project=None, os_inherit_extension_inherited=False, **kwargs):
160        """List roles and role grants.
161
162        :param user: filter in role grants for the specified user on a
163                     resource. Domain or project must be specified.
164                     User and group are mutually exclusive.
165        :type user: str or :class:`keystoneclient.v3.users.User`
166        :param group: filter in role grants for the specified group on a
167                      resource. Domain or project must be specified.
168                      User and group are mutually exclusive.
169        :type group: str or :class:`keystoneclient.v3.groups.Group`
170        :param domain: filter in role grants on the specified domain. Either
171                       user or group must be specified. Project, domain, and
172                       system are mutually exclusive.
173        :type domain: str or :class:`keystoneclient.v3.domains.Domain`
174        :param project: filter in role grants on the specified project. Either
175                       user or group must be specified. Project, domain and
176                       system are mutually exclusive.
177        :type project: str or :class:`keystoneclient.v3.projects.Project`
178        :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
179                                                    It provides the ability for
180                                                    projects to inherit role
181                                                    assignments from their
182                                                    domains or from parent
183                                                    projects in the hierarchy.
184        :param kwargs: any other attribute provided will filter roles on.
185
186        :returns: a list of roles.
187        :rtype: list of :class:`keystoneclient.v3.roles.Role`
188
189        """
190        if os_inherit_extension_inherited:
191            kwargs['tail'] = '/inherited_to_projects'
192        if user or group:
193            self._require_user_xor_group(user, group)
194            self._enforce_mutually_exclusive_group(system, domain, project)
195
196            base_url = self._role_grants_base_url(
197                user, group, system, domain, project,
198                os_inherit_extension_inherited
199            )
200            return super(RoleManager, self).list(base_url=base_url,
201                                                 **kwargs)
202
203        return super(RoleManager, self).list(**kwargs)
204
205    def update(self, role, name=None, **kwargs):
206        """Update a role.
207
208        :param role: the role to be updated on the server.
209        :type role: str or :class:`keystoneclient.v3.roles.Role`
210        :param str name: the new name of the role.
211        :param kwargs: any other attribute provided will be passed to server.
212
213        :returns: the updated role returned from server.
214        :rtype: :class:`keystoneclient.v3.roles.Role`
215
216        """
217        return super(RoleManager, self).update(
218            role_id=base.getid(role),
219            name=name,
220            **kwargs)
221
222    def delete(self, role):
223        """Delete a role.
224
225        When a role is deleted all the role inferences that have deleted role
226        as prior role will be deleted as well.
227
228        :param role: the role to be deleted on the server.
229        :type role: str or :class:`keystoneclient.v3.roles.Role`
230
231        :returns: Response object with 204 status.
232        :rtype: :class:`requests.models.Response`
233
234        """
235        return super(RoleManager, self).delete(
236            role_id=base.getid(role))
237
238    def grant(self, role, user=None, group=None, system=None, domain=None,
239              project=None, os_inherit_extension_inherited=False, **kwargs):
240        """Grant a role to a user or group on a domain or project.
241
242        :param role: the role to be granted on the server.
243        :type role: str or :class:`keystoneclient.v3.roles.Role`
244        :param user: the specified user to have the role granted on a resource.
245                     Domain or project must be specified. User and group are
246                     mutually exclusive.
247        :type user: str or :class:`keystoneclient.v3.users.User`
248        :param group: the specified group to have the role granted on a
249                      resource. Domain or project must be specified.
250                      User and group are mutually exclusive.
251        :type group: str or :class:`keystoneclient.v3.groups.Group`
252        :param system: system information to grant the role on. Project,
253                       domain, and system are mutually exclusive.
254        :type system: str
255        :param domain: the domain in which the role will be granted. Either
256                       user or group must be specified. Project, domain, and
257                       system are mutually exclusive.
258        :type domain: str or :class:`keystoneclient.v3.domains.Domain`
259        :param project: the project in which the role will be granted. Either
260                       user or group must be specified. Project, domain, and
261                       system are mutually exclusive.
262        :type project: str or :class:`keystoneclient.v3.projects.Project`
263        :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
264                                                    It provides the ability for
265                                                    projects to inherit role
266                                                    assignments from their
267                                                    domains or from parent
268                                                    projects in the hierarchy.
269        :param kwargs: any other attribute provided will be passed to server.
270
271        :returns: the granted role returned from server.
272        :rtype: :class:`keystoneclient.v3.roles.Role`
273
274        """
275        self._enforce_mutually_exclusive_group(system, domain, project)
276        self._require_user_xor_group(user, group)
277
278        if os_inherit_extension_inherited:
279            kwargs['tail'] = '/inherited_to_projects'
280
281        base_url = self._role_grants_base_url(
282            user, group, system, domain, project,
283            os_inherit_extension_inherited)
284        return super(RoleManager, self).put(base_url=base_url,
285                                            role_id=base.getid(role),
286                                            **kwargs)
287
288    def check(self, role, user=None, group=None, system=None, domain=None,
289              project=None, os_inherit_extension_inherited=False, **kwargs):
290        """Check if a user or group has a role on a domain or project.
291
292        :param user: check for role grants for the specified user on a
293                     resource. Domain or project must be specified.
294                     User and group are mutually exclusive.
295        :type user: str or :class:`keystoneclient.v3.users.User`
296        :param group: check for role grants for the specified group on a
297                      resource. Domain or project must be specified.
298                      User and group are mutually exclusive.
299        :type group: str or :class:`keystoneclient.v3.groups.Group`
300        :param system: check for role  grants on the system. Project, domain,
301                       and system are mutually exclusive.
302        :type system: str
303        :param domain: check for role grants on the specified domain. Either
304                       user or group must be specified. Project, domain, and
305                       system are mutually exclusive.
306        :type domain: str or :class:`keystoneclient.v3.domains.Domain`
307        :param project: check for role grants on the specified project. Either
308                       user or group must be specified. Project, domain, and
309                       system are mutually exclusive.
310        :type project: str or :class:`keystoneclient.v3.projects.Project`
311        :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
312                                                    It provides the ability for
313                                                    projects to inherit role
314                                                    assignments from their
315                                                    domains or from parent
316                                                    projects in the hierarchy.
317        :param kwargs: any other attribute provided will be passed to server.
318
319        :returns: the specified role returned from server if it exists.
320        :rtype: :class:`keystoneclient.v3.roles.Role`
321
322        :returns: Response object with 204 status if specified role
323                  doesn't exist.
324        :rtype: :class:`requests.models.Response`
325
326        """
327        self._enforce_mutually_exclusive_group(system, domain, project)
328        self._require_user_xor_group(user, group)
329
330        if os_inherit_extension_inherited:
331            kwargs['tail'] = '/inherited_to_projects'
332
333        base_url = self._role_grants_base_url(
334            user, group, system, domain, project,
335            os_inherit_extension_inherited)
336        return super(RoleManager, self).head(
337            base_url=base_url,
338            role_id=base.getid(role),
339            os_inherit_extension_inherited=os_inherit_extension_inherited,
340            **kwargs)
341
342    def revoke(self, role, user=None, group=None, system=None, domain=None,
343               project=None, os_inherit_extension_inherited=False, **kwargs):
344        """Revoke a role from a user or group on a domain or project.
345
346        :param user: revoke role grants for the specified user on a
347                     resource. Domain or project must be specified.
348                     User and group are mutually exclusive.
349        :type user: str or :class:`keystoneclient.v3.users.User`
350        :param group: revoke role grants for the specified group on a
351                      resource. Domain or project must be specified.
352                      User and group are mutually exclusive.
353        :type group: str or :class:`keystoneclient.v3.groups.Group`
354        :param system: revoke role grants on the system. Project, domain, and
355                       system are mutually exclusive.
356        :type system: str
357        :param domain: revoke role grants on the specified domain. Either
358                       user or group must be specified. Project, domain, and
359                       system are mutually exclusive.
360        :type domain: str or :class:`keystoneclient.v3.domains.Domain`
361        :param project: revoke role grants on the specified project. Either
362                       user or group must be specified. Project, domain, and
363                       system are mutually exclusive.
364        :type project: str or :class:`keystoneclient.v3.projects.Project`
365        :param bool os_inherit_extension_inherited: OS-INHERIT will be used.
366                                                    It provides the ability for
367                                                    projects to inherit role
368                                                    assignments from their
369                                                    domains or from parent
370                                                    projects in the hierarchy.
371        :param kwargs: any other attribute provided will be passed to server.
372
373        :returns: the revoked role returned from server.
374        :rtype: list of :class:`keystoneclient.v3.roles.Role`
375
376        """
377        self._enforce_mutually_exclusive_group(system, domain, project)
378        self._require_user_xor_group(user, group)
379
380        if os_inherit_extension_inherited:
381            kwargs['tail'] = '/inherited_to_projects'
382
383        base_url = self._role_grants_base_url(
384            user, group, system, domain, project,
385            os_inherit_extension_inherited)
386        return super(RoleManager, self).delete(
387            base_url=base_url,
388            role_id=base.getid(role),
389            os_inherit_extension_inherited=os_inherit_extension_inherited,
390            **kwargs)
391
392    @removals.remove(message='Use %s.create instead.' % deprecation_msg,
393                     version='3.9.0', removal_version='4.0.0')
394    def create_implied(self, prior_role, implied_role, **kwargs):
395        return InferenceRuleManager(self.client).create(prior_role,
396                                                        implied_role)
397
398    @removals.remove(message='Use %s.delete instead.' % deprecation_msg,
399                     version='3.9.0', removal_version='4.0.0')
400    def delete_implied(self, prior_role, implied_role, **kwargs):
401        return InferenceRuleManager(self.client).delete(prior_role,
402                                                        implied_role)
403
404    @removals.remove(message='Use %s.get instead.' % deprecation_msg,
405                     version='3.9.0', removal_version='4.0.0')
406    def get_implied(self, prior_role, implied_role, **kwargs):
407        return InferenceRuleManager(self.client).get(prior_role,
408                                                     implied_role)
409
410    @removals.remove(message='Use %s.check instead.' % deprecation_msg,
411                     version='3.9.0', removal_version='4.0.0')
412    def check_implied(self, prior_role, implied_role, **kwargs):
413        return InferenceRuleManager(self.client).check(prior_role,
414                                                       implied_role)
415
416    @removals.remove(message='Use %s.list_inference_roles' % deprecation_msg,
417                     version='3.9.0', removal_version='4.0.0')
418    def list_role_inferences(self, **kwargs):
419        return InferenceRuleManager(self.client).list_inference_roles()
420
421
422class InferenceRuleManager(base.CrudManager):
423    """Manager class for manipulating Identity inference rules."""
424
425    resource_class = InferenceRule
426    collection_key = 'role_inferences'
427    key = 'role_inference'
428
429    def _implied_role_url_tail(self, prior_role, implied_role):
430        base_url = ('/%(prior_role_id)s/implies/%(implied_role_id)s' %
431                    {'prior_role_id': base.getid(prior_role),
432                     'implied_role_id': base.getid(implied_role)})
433        return base_url
434
435    def create(self, prior_role, implied_role):
436        """Create an inference rule.
437
438        An inference rule is comprised of two roles, a prior role and an
439        implied role. The prior role will imply the implied role.
440
441        Valid HTTP return codes:
442
443            * 201: Resource is created successfully
444            * 404: A role cannot be found
445            * 409: The inference rule already exists
446
447        :param prior_role: the role which implies ``implied_role``.
448        :type role: str or :class:`keystoneclient.v3.roles.Role`
449        :param implied_role: the role which is implied by ``prior_role``.
450        :type role: str or :class:`keystoneclient.v3.roles.Role`
451
452        :returns: a newly created role inference returned from server.
453        :rtype: :class:`keystoneclient.v3.roles.InferenceRule`
454
455        """
456        url_tail = self._implied_role_url_tail(prior_role, implied_role)
457        _resp, body = self.client.put("/roles" + url_tail)
458        return self._prepare_return_value(
459            _resp, self.resource_class(self, body['role_inference']))
460
461    def delete(self, prior_role, implied_role):
462        """Delete an inference rule.
463
464        When deleting an inference rule, both roles are required. Note that
465        neither role is deleted, only the inference relationship is dissolved.
466
467        Valid HTTP return codes:
468
469            * 204: Delete request is accepted
470            * 404: A role cannot be found
471
472        :param prior_role: the role which implies ``implied_role``.
473        :type role: str or :class:`keystoneclient.v3.roles.Role`
474        :param implied_role: the role which is implied by ``prior_role``.
475        :type role: str or :class:`keystoneclient.v3.roles.Role`
476
477        :returns: Response object with 204 status.
478        :rtype: :class:`requests.models.Response`
479
480        """
481        url_tail = self._implied_role_url_tail(prior_role, implied_role)
482        return self._delete("/roles" + url_tail)
483
484    def get(self, prior_role, implied_role):
485        """Retrieve an inference rule.
486
487        Valid HTTP return codes:
488
489            * 200: Inference rule is returned
490            * 404: A role cannot be found
491
492        :param prior_role: the role which implies ``implied_role``.
493        :type role: str or :class:`keystoneclient.v3.roles.Role`
494        :param implied_role: the role which is implied by ``prior_role``.
495        :type role: str or :class:`keystoneclient.v3.roles.Role`
496
497        :returns: the specified role inference returned from server.
498        :rtype: :class:`keystoneclient.v3.roles.InferenceRule`
499
500        """
501        url_tail = self._implied_role_url_tail(prior_role, implied_role)
502        _resp, body = self.client.get("/roles" + url_tail)
503        return self._prepare_return_value(
504            _resp, self.resource_class(self, body['role_inference']))
505
506    def list(self, prior_role):
507        """List all roles that a role may imply.
508
509        Valid HTTP return codes:
510
511            * 200: List of inference rules are returned
512            * 404: A role cannot be found
513
514        :param prior_role: the role which implies ``implied_role``.
515        :type role: str or :class:`keystoneclient.v3.roles.Role`
516
517        :returns: the specified role inference returned from server.
518        :rtype: :class:`keystoneclient.v3.roles.InferenceRule`
519
520        """
521        url_tail = ('/%s/implies' % base.getid(prior_role))
522        _resp, body = self.client.get("/roles" + url_tail)
523        return self._prepare_return_value(
524            _resp, self.resource_class(self, body['role_inference']))
525
526    def check(self, prior_role, implied_role):
527        """Check if an inference rule exists.
528
529        Valid HTTP return codes:
530
531            * 204: The rule inference exists
532            * 404: A role cannot be found
533
534        :param prior_role: the role which implies ``implied_role``.
535        :type role: str or :class:`keystoneclient.v3.roles.Role`
536        :param implied_role: the role which is implied by ``prior_role``.
537        :type role: str or :class:`keystoneclient.v3.roles.Role`
538
539        :returns: response object with 204 status returned from server.
540        :rtype: :class:`requests.models.Response`
541
542        """
543        url_tail = self._implied_role_url_tail(prior_role, implied_role)
544        return self._head("/roles" + url_tail)
545
546    def list_inference_roles(self):
547        """List all rule inferences.
548
549        Valid HTTP return codes:
550
551            * 200: All inference rules are returned
552
553        :param kwargs: attributes provided will be passed to the server.
554
555        :returns: a list of inference rules.
556        :rtype: list of :class:`keystoneclient.v3.roles.InferenceRule`
557
558        """
559        return super(InferenceRuleManager, self).list()
560
561    def update(self, **kwargs):
562        raise exceptions.MethodNotImplemented(
563            _('Update not supported for rule inferences'))
564
565    def find(self, **kwargs):
566        raise exceptions.MethodNotImplemented(
567            _('Find not supported for rule inferences'))
568
569    def put(self, **kwargs):
570        raise exceptions.MethodNotImplemented(
571            _('Put not supported for rule inferences'))
572