diff --git a/doc/source/cli/command-objects/implied_role.rst b/doc/source/cli/command-objects/implied_role.rst new file mode 100644 index 0000000000..e43c9ea39e --- /dev/null +++ b/doc/source/cli/command-objects/implied_role.rst @@ -0,0 +1,57 @@ +============ +implied role +============ + +Identity v3 + + +implied role create +------------------- + +Creates an association between prior and implied roles + +.. program:: implied role create +.. code:: bash + + openstack implied role create + + --implied-role + +.. option:: + + Prior role (name or ID) implies another role + +.. option:: --implied-role + + (name or ID) implied by another role + + +implied role delete +------------------- + +Deletes an association between prior and implied roles + +.. program:: implied role delete +.. code:: bash + + openstack implied role delete + + --implied-role + +.. option:: + + Prior role (name or ID) implies another role + +.. option:: --implied-role + + (name or ID) implied by another role + +implied role list +----------------- + +List implied roles + +.. program:: implied role list +.. code:: bash + + openstack implied role list diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py new file mode 100644 index 0000000000..c762338997 --- /dev/null +++ b/openstackclient/identity/v3/implied_role.py @@ -0,0 +1,129 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Identity v3 Implied Role action implementations""" + +import logging + +from osc_lib.command import command +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +def _get_role_ids(identity_client, parsed_args): + """Return prior and implied role id(s) + + If prior and implied role id(s) are retrievable from identity + client, return tuple containing them. + """ + role_id = None + implied_role_id = None + + roles = identity_client.roles.list() + + for role in roles: + role_id_or_name = (role.name, role.id) + + if parsed_args.role in role_id_or_name: + role_id = role.id + elif parsed_args.implied_role in role_id_or_name: + implied_role_id = role.id + + return (role_id, implied_role_id) + + +class CreateImpliedRole(command.ShowOne): + + _description = _("Creates an association between prior and implied roles") + + def get_parser(self, prog_name): + parser = super(CreateImpliedRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help=_('Role (name or ID) that implies another role'), + ) + parser.add_argument( + '--implied-role', + metavar='', + help=' (name or ID) implied by another role', + required=True, + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + (prior_role_id, implied_role_id) = _get_role_ids( + identity_client, parsed_args) + response = identity_client.roles.create_implied( + prior_role_id, implied_role_id) + response._info.pop('links', None) + return zip(*sorted([(k, v['id']) + for k, v in six.iteritems(response._info)])) + + +class DeleteImpliedRole(command.Command): + + _description = _("Deletes an association between prior and implied roles") + + def get_parser(self, prog_name): + parser = super(DeleteImpliedRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help=_('Role (name or ID) that implies another role'), + ) + parser.add_argument( + '--implied-role', + metavar='', + help=' (name or ID) implied by another role', + required=True, + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + (prior_role_id, implied_role_id) = _get_role_ids( + identity_client, parsed_args) + identity_client.roles.delete_implied( + prior_role_id, implied_role_id) + + +class ListImpliedRole(command.Lister): + + _description = _("List implied roles") + _COLUMNS = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] + + def get_parser(self, prog_name): + parser = super(ListImpliedRole, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + def _list_implied(response): + for rule in response: + for implies in rule.implies: + yield (rule.prior_role['id'], + rule.prior_role['name'], + implies['id'], + implies['name']) + + identity_client = self.app.client_manager.identity + response = identity_client.roles.list_inference_roles() + return (self._COLUMNS, _list_implied(response)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 997bcf63e8..7de251524a 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -184,6 +184,8 @@ ROLE_2 = { 'links': base_url + 'roles/' + 'r2', } +ROLES = [ROLE, ROLE_2] + service_id = 's-123' service_name = 'Texaco' service_type = 'gas' @@ -968,3 +970,25 @@ class FakeRoleAssignment(object): info=copy.deepcopy(role_assignment_info), loaded=True) return role_assignment + + +class FakeImpliedRoleResponse(object): + """Fake one or more role assignment.""" + def __init__(self, prior_role, implied_roles): + self.prior_role = prior_role + self.implies = [role for role in implied_roles] + + @staticmethod + def create_list(): + """Create a fake implied role list response. + + :return: + A list of FakeImpliedRoleResponse objects + """ + + # set default attributes. + implied_roles = [ + FakeImpliedRoleResponse(ROLES[0], [ROLES[1]]) + ] + + return implied_roles diff --git a/openstackclient/tests/unit/identity/v3/test_implied_role.py b/openstackclient/tests/unit/identity/v3/test_implied_role.py new file mode 100644 index 0000000000..08273f7312 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_implied_role.py @@ -0,0 +1,181 @@ +# Copyright 2013 Nebula Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.identity.v3 import implied_role +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestRole(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestRole, self).setUp() + + # Get a shortcut to the UserManager Mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + # Get a shortcut to the UserManager Mock + self.groups_mock = self.app.client_manager.identity.groups + self.groups_mock.reset_mock() + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + # Get a shortcut to the RoleManager Mock + self.roles_mock = self.app.client_manager.identity.roles + self.roles_mock.reset_mock() + + def _is_inheritance_testcase(self): + return False + + +class TestImpliedRoleCreate(TestRole): + + def setUp(self): + super(TestImpliedRoleCreate, self).setUp() + + self.roles_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[0]), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[1]), + loaded=True, + ), + ] + + self.roles_mock.create_implied.return_value = fakes.FakeResource( + None, + {'prior_role': copy.deepcopy(identity_fakes.ROLES[0]), + 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, + loaded=True, + ) + + self.cmd = implied_role.CreateImpliedRole(self.app, None) + + def test_implied_role_create(self): + + arglist = [ + identity_fakes.ROLES[0]['id'], + '--implied-role', identity_fakes.ROLES[1]['id'], + ] + verifylist = [ + ('role', identity_fakes.ROLES[0]['id']), + ('implied_role', identity_fakes.ROLES[1]['id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # RoleManager.create_implied(prior, implied) + self.roles_mock.create_implied.assert_called_with( + identity_fakes.ROLES[0]['id'], + identity_fakes.ROLES[1]['id'] + ) + + collist = ('implied', 'prior_role') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.ROLES[1]['id'], + identity_fakes.ROLES[0]['id'] + ) + self.assertEqual(datalist, data) + + +class TestImpliedRoleDelete(TestRole): + + def setUp(self): + super(TestImpliedRoleDelete, self).setUp() + + self.roles_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[0]), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[1]), + loaded=True, + ), + ] + + self.roles_mock.delete_implied.return_value = fakes.FakeResource( + None, + {'prior-role': copy.deepcopy(identity_fakes.ROLES[0]), + 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, + loaded=True, + ) + + self.cmd = implied_role.DeleteImpliedRole(self.app, None) + + def test_implied_role_delete(self): + arglist = [ + identity_fakes.ROLES[0]['id'], + '--implied-role', identity_fakes.ROLES[1]['id'], + ] + verifylist = [ + ('role', identity_fakes.ROLES[0]['id']), + ('implied_role', identity_fakes.ROLES[1]['id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.roles_mock.delete_implied.assert_called_with( + identity_fakes.ROLES[0]['id'], + identity_fakes.ROLES[1]['id'] + ) + + +class TestImpliedRoleList(TestRole): + + def setUp(self): + super(TestImpliedRoleList, self).setUp() + + self.roles_mock.list_inference_roles.return_value = ( + identity_fakes.FakeImpliedRoleResponse.create_list()) + + self.cmd = implied_role.ListImpliedRole(self.app, None) + + def test_implied_role_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.roles_mock.list_inference_roles.assert_called_with() + + collist = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] + self.assertEqual(collist, columns) + datalist = [ + (identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[0]['name'], + identity_fakes.ROLES[1]['id'], identity_fakes.ROLES[1]['name']) + ] + x = [d for d in data] + self.assertEqual(datalist, x) diff --git a/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml b/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml new file mode 100644 index 0000000000..8d5d76eac1 --- /dev/null +++ b/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Support for creating, deleting, and listing implied roles has been added. + This allows users to create an inference rule between two roles. The + first, called the prior role is the role explicitly assigned to an + individual. The second, called the implied role, is one that the user + is assgined implicitly. Additionally, these rules can be chained, such + that an implied role from the first inference rule can be the implied role + in the second. Thus one explicitly assigned role can lead to multiple + implied roles. + ``implied role create --implied-role `` creates an + association between prior and implied roles. + ``implied role delete --implied-role `` removes an + association between prior and implied roles. + ``implied role list`` Lists all implied roles that currently exist. diff --git a/setup.cfg b/setup.cfg index ec91988f10..b4b6827d0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -247,6 +247,10 @@ openstack.identity.v3 = identity_provider_set = openstackclient.identity.v3.identity_provider:SetIdentityProvider identity_provider_show = openstackclient.identity.v3.identity_provider:ShowIdentityProvider + implied_role_create = openstackclient.identity.v3.implied_role:CreateImpliedRole + implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole + implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole + mapping_create = openstackclient.identity.v3.mapping:CreateMapping mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping mapping_list = openstackclient.identity.v3.mapping:ListMapping