Add support for app cred access rules
This commit introduces the --access-rules option for 'application credential create' as well as new 'access rule' commands for listing, showing, and deleting access rules. bp whitelist-extension-for-app-creds Change-Id: I04834b2874ec2a70da456a380b5bef03a392effa
This commit is contained in:
		
				
					committed by
					
						
						Colleen Murphy
					
				
			
			
				
	
			
			
			
						parent
						
							db29e28b7c
						
					
				
				
					commit
					70ab3f9dd5
				
			
							
								
								
									
										61
									
								
								doc/source/cli/command-objects/access-rules.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								doc/source/cli/command-objects/access-rules.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
===========
 | 
			
		||||
access rule
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Identity v3
 | 
			
		||||
 | 
			
		||||
Access rules are fine-grained permissions for application credentials. An access
 | 
			
		||||
rule comprises of a service type, a request path, and a request method. Access
 | 
			
		||||
rules may only be created as attributes of application credentials, but they may
 | 
			
		||||
be viewed and deleted independently.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
access rule delete
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
Delete access rule(s)
 | 
			
		||||
 | 
			
		||||
.. program:: access rule delete
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
    openstack access rule delete <access-rule> [<access-rule> ...]
 | 
			
		||||
 | 
			
		||||
.. describe:: <access-rule>
 | 
			
		||||
 | 
			
		||||
    Access rule(s) to delete (ID)
 | 
			
		||||
 | 
			
		||||
access rule list
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
List access rules
 | 
			
		||||
 | 
			
		||||
.. program:: access rule list
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
    openstack access rule list
 | 
			
		||||
        [--user <user>]
 | 
			
		||||
        [--user-domain <user-domain>]
 | 
			
		||||
 | 
			
		||||
.. option:: --user
 | 
			
		||||
 | 
			
		||||
    User whose access rules to list (name or ID). If not provided, looks up the
 | 
			
		||||
    current user's access rules.
 | 
			
		||||
 | 
			
		||||
.. option:: --user-domain
 | 
			
		||||
 | 
			
		||||
    Domain the user belongs to (name or ID). This can be
 | 
			
		||||
    used in case collisions between user names exist.
 | 
			
		||||
 | 
			
		||||
access rule show
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
Display access rule details
 | 
			
		||||
 | 
			
		||||
.. program:: access rule show
 | 
			
		||||
.. code:: bash
 | 
			
		||||
 | 
			
		||||
    openstack access rule show <access-rule>
 | 
			
		||||
 | 
			
		||||
.. describe:: <access-rule>
 | 
			
		||||
 | 
			
		||||
    Access rule to display (ID)
 | 
			
		||||
@@ -22,6 +22,7 @@ Create new application credential
 | 
			
		||||
        [--expiration <expiration>]
 | 
			
		||||
        [--description <description>]
 | 
			
		||||
        [--restricted|--unrestricted]
 | 
			
		||||
        [--access-rules <access-rules>]
 | 
			
		||||
        <name>
 | 
			
		||||
 | 
			
		||||
.. option:: --secret <secret>
 | 
			
		||||
@@ -52,6 +53,12 @@ Create new application credential
 | 
			
		||||
    Prohibit application credential from creating and deleting other
 | 
			
		||||
    application credentials and trusts (this is the default behavior)
 | 
			
		||||
 | 
			
		||||
.. option:: --access-rules
 | 
			
		||||
 | 
			
		||||
   Either a string or file path containing a JSON-formatted list of access
 | 
			
		||||
   rules, each containing a request method, path, and service, for example
 | 
			
		||||
   '[{"method": "GET", "path": "/v2.1/servers", "service": "compute"}]'
 | 
			
		||||
 | 
			
		||||
.. describe:: <name>
 | 
			
		||||
 | 
			
		||||
    Name of the application credential
 | 
			
		||||
 
 | 
			
		||||
@@ -95,7 +95,7 @@ python-heatclient==1.10.0
 | 
			
		||||
python-ironic-inspector-client==1.5.0
 | 
			
		||||
python-ironicclient==2.3.0
 | 
			
		||||
python-karborclient==0.6.0
 | 
			
		||||
python-keystoneclient==3.17.0
 | 
			
		||||
python-keystoneclient==3.22.0
 | 
			
		||||
python-mimeparse==1.6.0
 | 
			
		||||
python-mistralclient==3.1.0
 | 
			
		||||
python-muranoclient==0.8.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										118
									
								
								openstackclient/identity/v3/access_rule.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								openstackclient/identity/v3/access_rule.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
#   Copyright 2019 SUSE LLC
 | 
			
		||||
#
 | 
			
		||||
#   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 Access Rule action implementations"""
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from osc_lib.command import command
 | 
			
		||||
from osc_lib import exceptions
 | 
			
		||||
from osc_lib import utils
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from openstackclient.i18n import _
 | 
			
		||||
from openstackclient.identity import common
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeleteAccessRule(command.Command):
 | 
			
		||||
    _description = _("Delete access rule(s)")
 | 
			
		||||
 | 
			
		||||
    def get_parser(self, prog_name):
 | 
			
		||||
        parser = super(DeleteAccessRule, self).get_parser(prog_name)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            'access_rule',
 | 
			
		||||
            metavar='<access-rule>',
 | 
			
		||||
            nargs="+",
 | 
			
		||||
            help=_('Application credentials(s) to delete (name or ID)'),
 | 
			
		||||
        )
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def take_action(self, parsed_args):
 | 
			
		||||
        identity_client = self.app.client_manager.identity
 | 
			
		||||
 | 
			
		||||
        errors = 0
 | 
			
		||||
        for ac in parsed_args.access_rule:
 | 
			
		||||
            try:
 | 
			
		||||
                access_rule = utils.find_resource(
 | 
			
		||||
                    identity_client.access_rules, ac)
 | 
			
		||||
                identity_client.access_rules.delete(access_rule.id)
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                errors += 1
 | 
			
		||||
                LOG.error(_("Failed to delete access rule with "
 | 
			
		||||
                          "ID '%(ac)s': %(e)s"),
 | 
			
		||||
                          {'ac': ac, 'e': e})
 | 
			
		||||
 | 
			
		||||
        if errors > 0:
 | 
			
		||||
            total = len(parsed_args.access_rule)
 | 
			
		||||
            msg = (_("%(errors)s of %(total)s access rules failed "
 | 
			
		||||
                   "to delete.") % {'errors': errors, 'total': total})
 | 
			
		||||
            raise exceptions.CommandError(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ListAccessRule(command.Lister):
 | 
			
		||||
    _description = _("List access rules")
 | 
			
		||||
 | 
			
		||||
    def get_parser(self, prog_name):
 | 
			
		||||
        parser = super(ListAccessRule, self).get_parser(prog_name)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--user',
 | 
			
		||||
            metavar='<user>',
 | 
			
		||||
            help=_('User whose access rules to list (name or ID)'),
 | 
			
		||||
        )
 | 
			
		||||
        common.add_user_domain_option_to_parser(parser)
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def take_action(self, parsed_args):
 | 
			
		||||
        identity_client = self.app.client_manager.identity
 | 
			
		||||
        if parsed_args.user:
 | 
			
		||||
            user_id = common.find_user(identity_client,
 | 
			
		||||
                                       parsed_args.user,
 | 
			
		||||
                                       parsed_args.user_domain).id
 | 
			
		||||
        else:
 | 
			
		||||
            user_id = None
 | 
			
		||||
 | 
			
		||||
        columns = ('ID', 'Service', 'Method', 'Path')
 | 
			
		||||
        data = identity_client.access_rules.list(
 | 
			
		||||
            user=user_id)
 | 
			
		||||
        return (columns,
 | 
			
		||||
                (utils.get_item_properties(
 | 
			
		||||
                    s, columns,
 | 
			
		||||
                    formatters={},
 | 
			
		||||
                ) for s in data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowAccessRule(command.ShowOne):
 | 
			
		||||
    _description = _("Display access rule details")
 | 
			
		||||
 | 
			
		||||
    def get_parser(self, prog_name):
 | 
			
		||||
        parser = super(ShowAccessRule, self).get_parser(prog_name)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            'access_rule',
 | 
			
		||||
            metavar='<access-rule>',
 | 
			
		||||
            help=_('Application credential to display (name or ID)'),
 | 
			
		||||
        )
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def take_action(self, parsed_args):
 | 
			
		||||
        identity_client = self.app.client_manager.identity
 | 
			
		||||
        access_rule = utils.find_resource(identity_client.access_rules,
 | 
			
		||||
                                          parsed_args.access_rule)
 | 
			
		||||
 | 
			
		||||
        access_rule._info.pop('links', None)
 | 
			
		||||
 | 
			
		||||
        return zip(*sorted(six.iteritems(access_rule._info)))
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
"""Identity v3 Application Credential action implementations"""
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
from osc_lib.command import command
 | 
			
		||||
@@ -79,6 +80,17 @@ class CreateApplicationCredential(command.ShowOne):
 | 
			
		||||
                   ' other application credentials and trusts (this is the'
 | 
			
		||||
                   ' default behavior)'),
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--access-rules',
 | 
			
		||||
            metavar='<access-rules>',
 | 
			
		||||
            help=_('Either a string or file path containing a JSON-formatted '
 | 
			
		||||
                   'list of access rules, each containing a request method, '
 | 
			
		||||
                   'path, and service, for example '
 | 
			
		||||
                   '\'[{"method": "GET", '
 | 
			
		||||
                   '"path": "/v2.1/servers", '
 | 
			
		||||
                   '"service": "compute"}]\''),
 | 
			
		||||
 | 
			
		||||
        )
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def take_action(self, parsed_args):
 | 
			
		||||
@@ -105,6 +117,20 @@ class CreateApplicationCredential(command.ShowOne):
 | 
			
		||||
        else:
 | 
			
		||||
            unrestricted = parsed_args.unrestricted
 | 
			
		||||
 | 
			
		||||
        if parsed_args.access_rules:
 | 
			
		||||
            try:
 | 
			
		||||
                access_rules = json.loads(parsed_args.access_rules)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                try:
 | 
			
		||||
                    with open(parsed_args.access_rules) as f:
 | 
			
		||||
                        access_rules = json.load(f)
 | 
			
		||||
                except IOError:
 | 
			
		||||
                    raise exceptions.CommandError(
 | 
			
		||||
                        _("Access rules is not valid JSON string or file does"
 | 
			
		||||
                          " not exist."))
 | 
			
		||||
        else:
 | 
			
		||||
            access_rules = None
 | 
			
		||||
 | 
			
		||||
        app_cred_manager = identity_client.application_credentials
 | 
			
		||||
        application_credential = app_cred_manager.create(
 | 
			
		||||
            parsed_args.name,
 | 
			
		||||
@@ -113,6 +139,7 @@ class CreateApplicationCredential(command.ShowOne):
 | 
			
		||||
            description=parsed_args.description,
 | 
			
		||||
            secret=parsed_args.secret,
 | 
			
		||||
            unrestricted=unrestricted,
 | 
			
		||||
            access_rules=access_rules,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        application_credential._info.pop('links', None)
 | 
			
		||||
 
 | 
			
		||||
@@ -470,6 +470,14 @@ app_cred_description = 'app credential for testing'
 | 
			
		||||
app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0)
 | 
			
		||||
app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z')
 | 
			
		||||
app_cred_secret = 'moresecuresecret'
 | 
			
		||||
app_cred_access_rules = (
 | 
			
		||||
    '[{"path": "/v2.1/servers", "method": "GET", "service": "compute"}]'
 | 
			
		||||
)
 | 
			
		||||
app_cred_access_rules_path = '/tmp/access_rules.json'
 | 
			
		||||
access_rule_id = 'access-rule-id'
 | 
			
		||||
access_rule_service = 'compute'
 | 
			
		||||
access_rule_path = '/v2.1/servers'
 | 
			
		||||
access_rule_method = 'GET'
 | 
			
		||||
APP_CRED_BASIC = {
 | 
			
		||||
    'id': app_cred_id,
 | 
			
		||||
    'name': app_cred_name,
 | 
			
		||||
@@ -478,7 +486,8 @@ APP_CRED_BASIC = {
 | 
			
		||||
    'description': None,
 | 
			
		||||
    'expires_at': None,
 | 
			
		||||
    'unrestricted': False,
 | 
			
		||||
    'secret': app_cred_secret
 | 
			
		||||
    'secret': app_cred_secret,
 | 
			
		||||
    'access_rules': None
 | 
			
		||||
}
 | 
			
		||||
APP_CRED_OPTIONS = {
 | 
			
		||||
    'id': app_cred_id,
 | 
			
		||||
@@ -488,7 +497,25 @@ APP_CRED_OPTIONS = {
 | 
			
		||||
    'description': app_cred_description,
 | 
			
		||||
    'expires_at': app_cred_expires_str,
 | 
			
		||||
    'unrestricted': False,
 | 
			
		||||
    'secret': app_cred_secret
 | 
			
		||||
    'secret': app_cred_secret,
 | 
			
		||||
    'access_rules': None,
 | 
			
		||||
}
 | 
			
		||||
ACCESS_RULE = {
 | 
			
		||||
    'id': access_rule_id,
 | 
			
		||||
    'service': access_rule_service,
 | 
			
		||||
    'path': access_rule_path,
 | 
			
		||||
    'method': access_rule_method,
 | 
			
		||||
}
 | 
			
		||||
APP_CRED_ACCESS_RULES = {
 | 
			
		||||
    'id': app_cred_id,
 | 
			
		||||
    'name': app_cred_name,
 | 
			
		||||
    'project_id': project_id,
 | 
			
		||||
    'roles': app_cred_role,
 | 
			
		||||
    'description': None,
 | 
			
		||||
    'expires_at': None,
 | 
			
		||||
    'unrestricted': False,
 | 
			
		||||
    'secret': app_cred_secret,
 | 
			
		||||
    'access_rules': app_cred_access_rules
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
registered_limit_id = 'registered-limit-id'
 | 
			
		||||
@@ -625,6 +652,8 @@ class FakeIdentityv3Client(object):
 | 
			
		||||
        self.application_credentials = mock.Mock()
 | 
			
		||||
        self.application_credentials.resource_class = fakes.FakeResource(None,
 | 
			
		||||
                                                                         {})
 | 
			
		||||
        self.access_rules = mock.Mock()
 | 
			
		||||
        self.access_rules.resource_class = fakes.FakeResource(None, {})
 | 
			
		||||
        self.inference_rules = mock.Mock()
 | 
			
		||||
        self.inference_rules.resource_class = fakes.FakeResource(None, {})
 | 
			
		||||
        self.registered_limits = mock.Mock()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										174
									
								
								openstackclient/tests/unit/identity/v3/test_access_rule.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								openstackclient/tests/unit/identity/v3/test_access_rule.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
#   Copyright 2019 SUSE LLC
 | 
			
		||||
#
 | 
			
		||||
#   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
 | 
			
		||||
 | 
			
		||||
import mock
 | 
			
		||||
from osc_lib import exceptions
 | 
			
		||||
from osc_lib import utils
 | 
			
		||||
 | 
			
		||||
from openstackclient.identity.v3 import access_rule
 | 
			
		||||
from openstackclient.tests.unit import fakes
 | 
			
		||||
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAccessRule(identity_fakes.TestIdentityv3):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestAccessRule, self).setUp()
 | 
			
		||||
 | 
			
		||||
        identity_manager = self.app.client_manager.identity
 | 
			
		||||
        self.access_rules_mock = identity_manager.access_rules
 | 
			
		||||
        self.access_rules_mock.reset_mock()
 | 
			
		||||
        self.roles_mock = identity_manager.roles
 | 
			
		||||
        self.roles_mock.reset_mock()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAccessRuleDelete(TestAccessRule):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestAccessRuleDelete, self).setUp()
 | 
			
		||||
 | 
			
		||||
        # This is the return value for utils.find_resource()
 | 
			
		||||
        self.access_rules_mock.get.return_value = fakes.FakeResource(
 | 
			
		||||
            None,
 | 
			
		||||
            copy.deepcopy(identity_fakes.ACCESS_RULE),
 | 
			
		||||
            loaded=True,
 | 
			
		||||
        )
 | 
			
		||||
        self.access_rules_mock.delete.return_value = None
 | 
			
		||||
 | 
			
		||||
        # Get the command object to test
 | 
			
		||||
        self.cmd = access_rule.DeleteAccessRule(
 | 
			
		||||
            self.app, None)
 | 
			
		||||
 | 
			
		||||
    def test_access_rule_delete(self):
 | 
			
		||||
        arglist = [
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
        ]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('access_rule', [identity_fakes.access_rule_id])
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        result = self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
        self.access_rules_mock.delete.assert_called_with(
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertIsNone(result)
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(utils, 'find_resource')
 | 
			
		||||
    def test_delete_multi_access_rules_with_exception(self, find_mock):
 | 
			
		||||
        find_mock.side_effect = [self.access_rules_mock.get.return_value,
 | 
			
		||||
                                 exceptions.CommandError]
 | 
			
		||||
        arglist = [
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
            'nonexistent_access_rule',
 | 
			
		||||
        ]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('access_rule', arglist),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.cmd.take_action(parsed_args)
 | 
			
		||||
            self.fail('CommandError should be raised.')
 | 
			
		||||
        except exceptions.CommandError as e:
 | 
			
		||||
            self.assertEqual('1 of 2 access rules failed to'
 | 
			
		||||
                             ' delete.', str(e))
 | 
			
		||||
 | 
			
		||||
        find_mock.assert_any_call(self.access_rules_mock,
 | 
			
		||||
                                  identity_fakes.access_rule_id)
 | 
			
		||||
        find_mock.assert_any_call(self.access_rules_mock,
 | 
			
		||||
                                  'nonexistent_access_rule')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(2, find_mock.call_count)
 | 
			
		||||
        self.access_rules_mock.delete.assert_called_once_with(
 | 
			
		||||
            identity_fakes.access_rule_id)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAccessRuleList(TestAccessRule):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestAccessRuleList, self).setUp()
 | 
			
		||||
 | 
			
		||||
        self.access_rules_mock.list.return_value = [
 | 
			
		||||
            fakes.FakeResource(
 | 
			
		||||
                None,
 | 
			
		||||
                copy.deepcopy(identity_fakes.ACCESS_RULE),
 | 
			
		||||
                loaded=True,
 | 
			
		||||
            ),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        # Get the command object to test
 | 
			
		||||
        self.cmd = access_rule.ListAccessRule(self.app, None)
 | 
			
		||||
 | 
			
		||||
    def test_access_rule_list(self):
 | 
			
		||||
        arglist = []
 | 
			
		||||
        verifylist = []
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
        self.access_rules_mock.list.assert_called_with(user=None)
 | 
			
		||||
 | 
			
		||||
        collist = ('ID', 'Service', 'Method', 'Path')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = ((
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
            identity_fakes.access_rule_service,
 | 
			
		||||
            identity_fakes.access_rule_method,
 | 
			
		||||
            identity_fakes.access_rule_path,
 | 
			
		||||
        ), )
 | 
			
		||||
        self.assertEqual(datalist, tuple(data))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAccessRuleShow(TestAccessRule):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestAccessRuleShow, self).setUp()
 | 
			
		||||
 | 
			
		||||
        self.access_rules_mock.get.return_value = fakes.FakeResource(
 | 
			
		||||
            None,
 | 
			
		||||
            copy.deepcopy(identity_fakes.ACCESS_RULE),
 | 
			
		||||
            loaded=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Get the command object to test
 | 
			
		||||
        self.cmd = access_rule.ShowAccessRule(self.app, None)
 | 
			
		||||
 | 
			
		||||
    def test_access_rule_show(self):
 | 
			
		||||
        arglist = [
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
        ]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('access_rule', identity_fakes.access_rule_id),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
        self.access_rules_mock.get.assert_called_with(
 | 
			
		||||
            identity_fakes.access_rule_id)
 | 
			
		||||
 | 
			
		||||
        collist = ('id', 'method', 'path', 'service')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            identity_fakes.access_rule_id,
 | 
			
		||||
            identity_fakes.access_rule_method,
 | 
			
		||||
            identity_fakes.access_rule_path,
 | 
			
		||||
            identity_fakes.access_rule_service,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(datalist, data)
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import json
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
from osc_lib import exceptions
 | 
			
		||||
@@ -79,16 +80,18 @@ class TestApplicationCredentialCreate(TestApplicationCredential):
 | 
			
		||||
            'expires_at': None,
 | 
			
		||||
            'description': None,
 | 
			
		||||
            'unrestricted': False,
 | 
			
		||||
            'access_rules': None,
 | 
			
		||||
        }
 | 
			
		||||
        self.app_creds_mock.create.assert_called_with(
 | 
			
		||||
            name,
 | 
			
		||||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
 | 
			
		||||
                   'roles', 'secret', 'unrestricted')
 | 
			
		||||
        collist = ('access_rules', 'description', 'expires_at', 'id', 'name',
 | 
			
		||||
                   'project_id', 'roles', 'secret', 'unrestricted')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            identity_fakes.app_cred_id,
 | 
			
		||||
@@ -135,17 +138,19 @@ class TestApplicationCredentialCreate(TestApplicationCredential):
 | 
			
		||||
            'roles': [identity_fakes.role_id],
 | 
			
		||||
            'expires_at': identity_fakes.app_cred_expires,
 | 
			
		||||
            'description': 'credential for testing',
 | 
			
		||||
            'unrestricted': False
 | 
			
		||||
            'unrestricted': False,
 | 
			
		||||
            'access_rules': None,
 | 
			
		||||
        }
 | 
			
		||||
        self.app_creds_mock.create.assert_called_with(
 | 
			
		||||
            name,
 | 
			
		||||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
 | 
			
		||||
                   'roles', 'secret', 'unrestricted')
 | 
			
		||||
        collist = ('access_rules', 'description', 'expires_at', 'id', 'name',
 | 
			
		||||
                   'project_id', 'roles', 'secret', 'unrestricted')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            None,
 | 
			
		||||
            identity_fakes.app_cred_description,
 | 
			
		||||
            identity_fakes.app_cred_expires_str,
 | 
			
		||||
            identity_fakes.app_cred_id,
 | 
			
		||||
@@ -157,6 +162,111 @@ class TestApplicationCredentialCreate(TestApplicationCredential):
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(datalist, data)
 | 
			
		||||
 | 
			
		||||
    def test_application_credential_create_with_access_rules_string(self):
 | 
			
		||||
        name = identity_fakes.app_cred_name
 | 
			
		||||
        self.app_creds_mock.create.return_value = fakes.FakeResource(
 | 
			
		||||
            None,
 | 
			
		||||
            copy.deepcopy(identity_fakes.APP_CRED_ACCESS_RULES),
 | 
			
		||||
            loaded=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        arglist = [
 | 
			
		||||
            name,
 | 
			
		||||
            '--access-rules', identity_fakes.app_cred_access_rules,
 | 
			
		||||
        ]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('name', identity_fakes.app_cred_name),
 | 
			
		||||
            ('access_rules', identity_fakes.app_cred_access_rules),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
        # Set expected values
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'secret': None,
 | 
			
		||||
            'roles': [],
 | 
			
		||||
            'expires_at': None,
 | 
			
		||||
            'description': None,
 | 
			
		||||
            'unrestricted': False,
 | 
			
		||||
            'access_rules': json.loads(identity_fakes.app_cred_access_rules)
 | 
			
		||||
        }
 | 
			
		||||
        self.app_creds_mock.create.assert_called_with(
 | 
			
		||||
            name,
 | 
			
		||||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        collist = ('access_rules', 'description', 'expires_at', 'id', 'name',
 | 
			
		||||
                   'project_id', 'roles', 'secret', 'unrestricted')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            identity_fakes.app_cred_access_rules,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            identity_fakes.app_cred_id,
 | 
			
		||||
            identity_fakes.app_cred_name,
 | 
			
		||||
            identity_fakes.project_id,
 | 
			
		||||
            identity_fakes.role_name,
 | 
			
		||||
            identity_fakes.app_cred_secret,
 | 
			
		||||
            False,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(datalist, data)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('openstackclient.identity.v3.application_credential.json.load')
 | 
			
		||||
    @mock.patch('openstackclient.identity.v3.application_credential.open')
 | 
			
		||||
    def test_application_credential_create_with_access_rules_file(
 | 
			
		||||
            self, _, mock_json_load):
 | 
			
		||||
        mock_json_load.return_value = identity_fakes.app_cred_access_rules
 | 
			
		||||
 | 
			
		||||
        name = identity_fakes.app_cred_name
 | 
			
		||||
        self.app_creds_mock.create.return_value = fakes.FakeResource(
 | 
			
		||||
            None,
 | 
			
		||||
            copy.deepcopy(identity_fakes.APP_CRED_ACCESS_RULES),
 | 
			
		||||
            loaded=True,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        arglist = [
 | 
			
		||||
            name,
 | 
			
		||||
            '--access-rules', identity_fakes.app_cred_access_rules_path,
 | 
			
		||||
        ]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('name', identity_fakes.app_cred_name),
 | 
			
		||||
            ('access_rules', identity_fakes.app_cred_access_rules_path),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
			
		||||
 | 
			
		||||
        # Set expected values
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            'secret': None,
 | 
			
		||||
            'roles': [],
 | 
			
		||||
            'expires_at': None,
 | 
			
		||||
            'description': None,
 | 
			
		||||
            'unrestricted': False,
 | 
			
		||||
            'access_rules': identity_fakes.app_cred_access_rules
 | 
			
		||||
        }
 | 
			
		||||
        self.app_creds_mock.create.assert_called_with(
 | 
			
		||||
            name,
 | 
			
		||||
            **kwargs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        collist = ('access_rules', 'description', 'expires_at', 'id', 'name',
 | 
			
		||||
                   'project_id', 'roles', 'secret', 'unrestricted')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            identity_fakes.app_cred_access_rules,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            identity_fakes.app_cred_id,
 | 
			
		||||
            identity_fakes.app_cred_name,
 | 
			
		||||
            identity_fakes.project_id,
 | 
			
		||||
            identity_fakes.role_name,
 | 
			
		||||
            identity_fakes.app_cred_secret,
 | 
			
		||||
            False,
 | 
			
		||||
        )
 | 
			
		||||
        self.assertEqual(datalist, data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestApplicationCredentialDelete(TestApplicationCredential):
 | 
			
		||||
 | 
			
		||||
@@ -293,10 +403,11 @@ class TestApplicationCredentialShow(TestApplicationCredential):
 | 
			
		||||
 | 
			
		||||
        self.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id)
 | 
			
		||||
 | 
			
		||||
        collist = ('description', 'expires_at', 'id', 'name', 'project_id',
 | 
			
		||||
                   'roles', 'secret', 'unrestricted')
 | 
			
		||||
        collist = ('access_rules', 'description', 'expires_at', 'id', 'name',
 | 
			
		||||
                   'project_id', 'roles', 'secret', 'unrestricted')
 | 
			
		||||
        self.assertEqual(collist, columns)
 | 
			
		||||
        datalist = (
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            identity_fakes.app_cred_id,
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
features:
 | 
			
		||||
  - |
 | 
			
		||||
    [`blueprint whitelist-extension-for-app-creds <https://blueprints.launchpad.net/keystone/+spec/whitelist-extension-for-app-creds>`_]
 | 
			
		||||
    Added support for creating access rules as an attribute of application
 | 
			
		||||
    credentials as well as for listing, showing, and deleting access rules.
 | 
			
		||||
@@ -12,6 +12,6 @@ osc-lib>=2.0.0 # Apache-2.0
 | 
			
		||||
oslo.i18n>=3.15.3 # Apache-2.0
 | 
			
		||||
oslo.utils>=3.33.0 # Apache-2.0
 | 
			
		||||
python-glanceclient>=2.8.0 # Apache-2.0
 | 
			
		||||
python-keystoneclient>=3.17.0 # Apache-2.0
 | 
			
		||||
python-keystoneclient>=3.22.0 # Apache-2.0
 | 
			
		||||
python-novaclient>=15.1.0 # Apache-2.0
 | 
			
		||||
python-cinderclient>=3.3.0 # Apache-2.0
 | 
			
		||||
 
 | 
			
		||||
@@ -197,6 +197,10 @@ openstack.identity.v2 =
 | 
			
		||||
openstack.identity.v3 =
 | 
			
		||||
    access_token_create = openstackclient.identity.v3.token:CreateAccessToken
 | 
			
		||||
 | 
			
		||||
    access_rule_delete = openstackclient.identity.v3.access_rule:DeleteAccessRule
 | 
			
		||||
    access_rule_list = openstackclient.identity.v3.access_rule:ListAccessRule
 | 
			
		||||
    access_rule_show = openstackclient.identity.v3.access_rule:ShowAccessRule
 | 
			
		||||
 | 
			
		||||
    application_credential_create = openstackclient.identity.v3.application_credential:CreateApplicationCredential
 | 
			
		||||
    application_credential_delete = openstackclient.identity.v3.application_credential:DeleteApplicationCredential
 | 
			
		||||
    application_credential_list = openstackclient.identity.v3.application_credential:ListApplicationCredential
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user