Hello community, here is the log from the commit of package azure-cli-role for openSUSE:Factory checked in at 2018-02-14 09:31:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/azure-cli-role (Old) and /work/SRC/openSUSE:Factory/.azure-cli-role.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "azure-cli-role" Wed Feb 14 09:31:49 2018 rev:2 rq:574824 version:2.0.17 Changes: -------- --- /work/SRC/openSUSE:Factory/azure-cli-role/azure-cli-role.changes 2017-11-10 14:54:54.097494310 +0100 +++ /work/SRC/openSUSE:Factory/.azure-cli-role.new/azure-cli-role.changes 2018-02-14 09:31:55.670815092 +0100 @@ -1,0 +2,10 @@ +Thu Feb 8 13:43:34 UTC 2018 - [email protected] + +- New upstream release + + Version 2.0.17 + + For detailed information about changes see the + HISTORY.rst file provided with this package +- Install HISTORY.rst into doc directory +- Update Requires from setup.py + +------------------------------------------------------------------- Old: ---- azure-cli-role-2.0.12.tar.gz New: ---- azure-cli-role-2.0.17.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ azure-cli-role.spec ++++++ --- /var/tmp/diff_new_pack.rE3rb6/_old 2018-02-14 09:31:57.466750016 +0100 +++ /var/tmp/diff_new_pack.rE3rb6/_new 2018-02-14 09:31:57.470749871 +0100 @@ -1,7 +1,7 @@ # # spec file for package azure-cli-role # -# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -15,8 +15,9 @@ # Please submit bugfixes or comments via http://bugs.opensuse.org/ # + Name: azure-cli-role -Version: 2.0.12 +Version: 2.0.17 Release: 0 Summary: Microsoft Azure CLI 'role' Command Module for Role-Based Access Control (RBAC) License: MIT @@ -24,19 +25,19 @@ Url: https://github.com/Azure/azure-cli Source: https://files.pythonhosted.org/packages/source/a/azure-cli-role/azure-cli-role-%{version}.tar.gz Source1: LICENSE.txt -BuildRequires: python3-devel -BuildRequires: python3-setuptools -BuildRequires: unzip BuildRequires: azure-cli-command-modules-nspkg BuildRequires: azure-cli-nspkg BuildRequires: python3-azure-nspkg +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: unzip Requires: azure-cli-command-modules-nspkg -Requires: azure-cli-nspkg -Requires: python3-azure-nspkg Requires: azure-cli-core -Requires: python3-azure-mgmt-authorization >= 0.30.0 +Requires: azure-cli-nspkg Requires: python3-azure-graphrbac >= 0.31.0 -Requires: python3-azure-keyvault >= 0.3.6 +Requires: python3-azure-keyvault >= 0.3.7 +Requires: python3-azure-mgmt-authorization >= 0.30.0 +Requires: python3-azure-nspkg Conflicts: azure-cli < 2.0.0 BuildArch: noarch @@ -65,7 +66,8 @@ %files %defattr(-,root,root,-) -%doc LICENSE.txt README.rst +%doc HISTORY.rst LICENSE.txt README.rst %{python3_sitelib}/azure/cli/command_modules/role %{python3_sitelib}/azure_cli_role-*.egg-info + %changelog ++++++ azure-cli-role-2.0.12.tar.gz -> azure-cli-role-2.0.17.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/HISTORY.rst new/azure-cli-role-2.0.17/HISTORY.rst --- old/azure-cli-role-2.0.12/HISTORY.rst 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/HISTORY.rst 2018-01-12 18:25:22.000000000 +0100 @@ -2,6 +2,29 @@ Release History =============== + +2.0.17 +++++++ +* role assignment: expose --assignee-object-id to bypass graph query + +2.0.16 +++++++ +* Update for CLI core changes. + +2.0.15 +++++++ +* `role assignment list`: show default assignments for classic administrators +* `ad sp reset-credentials`: support to add credentials instead of overwriting +* `create-for-rbac`: emit out an actionable error if provisioning application failed for lack of permissions + +2.0.14 +++++++ +* minor fixes + +2.0.13 (2017-10-09) ++++++++++++++++++++ +* minor fixes + 2.0.12 (2017-09-22) +++++++++++++++++++ * minor fixes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/PKG-INFO new/azure-cli-role-2.0.17/PKG-INFO --- old/azure-cli-role-2.0.12/PKG-INFO 2017-09-23 01:49:20.000000000 +0200 +++ new/azure-cli-role-2.0.17/PKG-INFO 2018-01-12 18:25:50.000000000 +0100 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: azure-cli-role -Version: 2.0.12 +Version: 2.0.17 Summary: Microsoft Azure Command-Line Tools Role Command Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation Author-email: [email protected] License: MIT -Description-Content-Type: UNKNOWN Description: Microsoft Azure CLI 'role' Command Module for Role-Based Access Control (RBAC) ============================================================================== @@ -20,6 +19,29 @@ Release History =============== + + 2.0.17 + ++++++ + * role assignment: expose --assignee-object-id to bypass graph query + + 2.0.16 + ++++++ + * Update for CLI core changes. + + 2.0.15 + ++++++ + * `role assignment list`: show default assignments for classic administrators + * `ad sp reset-credentials`: support to add credentials instead of overwriting + * `create-for-rbac`: emit out an actionable error if provisioning application failed for lack of permissions + + 2.0.14 + ++++++ + * minor fixes + + 2.0.13 (2017-10-09) + +++++++++++++++++++ + * minor fixes + 2.0.12 (2017-09-22) +++++++++++++++++++ * minor fixes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/__init__.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/__init__.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/__init__.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/__init__.py 2018-01-12 18:25:22.000000000 +0100 @@ -3,12 +3,26 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +from azure.cli.core import AzCommandsLoader + import azure.cli.command_modules.role._help # pylint: disable=unused-import -def load_params(_): - import azure.cli.command_modules.role._params # pylint: disable=redefined-outer-name, unused-variable +class RoleCommandsLoader(AzCommandsLoader): + + def __init__(self, cli_ctx=None): + from azure.cli.core.commands import CliCommandType + role_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.role.custom#{}') + super(RoleCommandsLoader, self).__init__(cli_ctx=cli_ctx, custom_command_type=role_custom) + + def load_command_table(self, args): + from azure.cli.command_modules.role.commands import load_command_table + load_command_table(self, args) + return self.command_table + + def load_arguments(self, command): + from azure.cli.command_modules.role._params import load_arguments + load_arguments(self, command) -def load_commands(): - import azure.cli.command_modules.role.commands # pylint: disable=redefined-outer-name, unused-variable +COMMAND_LOADER_CLS = RoleCommandsLoader diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_client_factory.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_client_factory.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_client_factory.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_client_factory.py 2018-01-12 18:25:22.000000000 +0100 @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- -def _auth_client_factory(scope=None): +def _auth_client_factory(cli_ctx, scope=None): import re from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.mgmt.authorization import AuthorizationManagementClient @@ -13,18 +13,17 @@ matched = re.match('/subscriptions/(?P<subscription>[^/]*)/', scope) if matched: subscription_id = matched.groupdict()['subscription'] - return get_mgmt_service_client(AuthorizationManagementClient, subscription_id=subscription_id) + return get_mgmt_service_client(cli_ctx, AuthorizationManagementClient, subscription_id=subscription_id) -def _graph_client_factory(**_): - from azure.cli.core._profile import Profile, CLOUD +def _graph_client_factory(cli_ctx, **_): + from azure.cli.core._profile import Profile from azure.cli.core.commands.client_factory import configure_common_settings from azure.graphrbac import GraphRbacManagementClient - profile = Profile() + profile = Profile(cli_ctx=cli_ctx) cred, _, tenant_id = profile.get_login_credentials( - resource=CLOUD.endpoints.active_directory_graph_resource_id) - client = GraphRbacManagementClient(cred, - tenant_id, - base_url=CLOUD.endpoints.active_directory_graph_resource_id) - configure_common_settings(client) + resource=cli_ctx.cloud.endpoints.active_directory_graph_resource_id) + client = GraphRbacManagementClient(cred, tenant_id, + base_url=cli_ctx.cloud.endpoints.active_directory_graph_resource_id) + configure_common_settings(cli_ctx, client) return client diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_completers.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_completers.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_completers.py 1970-01-01 01:00:00.000000000 +0100 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_completers.py 2018-01-12 18:25:22.000000000 +0100 @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from azure.cli.core.decorators import Completer + +from azure.cli.command_modules.role.custom import list_role_definitions + + +@Completer +def get_role_definition_name_completion_list(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument + definitions = list_role_definitions(cmd) + return [x.properties.role_name for x in list(definitions)] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_help.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_help.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_help.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_help.py 2018-01-12 18:25:22.000000000 +0100 @@ -3,7 +3,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from azure.cli.core.help_files import helps # pylint: disable=unused-import +from knack.help_files import helps # pylint: disable=unused-import helps['ad sp create-for-rbac'] = """ @@ -47,8 +47,8 @@ - name: Create with customized contributor assignments. text: | az ad sp create-for-rbac -n "MyApp" --role contributor \\ - --scopes /subscriptions/{SubID}/resourceGroups/{MyRG1} \\ - /subscriptions/{SubID}/resourceGroups/{MyRG2} + --scopes /subscriptions/{SubID}/resourceGroups/{ResourceGroup1} \\ + /subscriptions/{SubID}/resourceGroups/{ResourceGroup2} - name: Create using a self-signed certificte. text: az ad sp create-for-rbac --create-cert - name: Create using a self-signed certificate, and store it within KeyVault. @@ -160,23 +160,23 @@ examples: - name: Create a role with read-only access to storage and network resources, and the ability to start or restart VMs. text: | - az role definition create --role-definition { \\ - "Name": "Contoso On-call", \\ - "Description": "Perform VM actions and read storange and network information." \\ - "Actions": [ \\ - "Microsoft.Compute/*/read", \\ - "Microsoft.Compute/virtualMachines/start/action", \\ - "Microsoft.Compute/virtualMachines/restart/action", \\ - "Microsoft.Network/*/read", \\ - "Microsoft.Storage/*/read", \\ - "Microsoft.Authorization/*/read", \\ - "Microsoft.Resources/subscriptions/resourceGroups/read", \\ - "Microsoft.Resources/subscriptions/resourceGroups/resources/read", \\ - "Microsoft.Insights/alertRules/*", \\ - "Microsoft.Support/*" \\ - ], \\ - "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] \\ - } + az role definition create --role-definition '{ + "Name": "Contoso On-call", + "Description": "Perform VM actions and read storage and network information." + "Actions": [ + "Microsoft.Compute/*/read", + "Microsoft.Compute/virtualMachines/start/action", + "Microsoft.Compute/virtualMachines/restart/action", + "Microsoft.Network/*/read", + "Microsoft.Storage/*/read", + "Microsoft.Authorization/*/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/subscriptions/resourceGroups/resources/read", + "Microsoft.Insights/alertRules/*", + "Microsoft.Support/*" + ], + "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + }' - name: Create a role from a file containing a JSON description. text: > az role definition create --role-definition @ad-role.json @@ -193,6 +193,33 @@ helps['role definition update'] = """ type: command short-summary: Update a role definition. + parameters: + - name: --role-definition + type: string + short-summary: Description of a role as JSON, or a path to a file containing a JSON description. + examples: + - name: Create a role with read-only access to storage and network resources, and the ability to start or restart VMs. + text: | + az role definition create --role-definition '{ + "Name": "Contoso On-call", + "Description": "Perform VM actions and read storage and network information." + "Actions": [ + "Microsoft.Compute/*/read", + "Microsoft.Compute/virtualMachines/start/action", + "Microsoft.Compute/virtualMachines/restart/action", + "Microsoft.Network/*/read", + "Microsoft.Storage/*/read", + "Microsoft.Authorization/*/read", + "Microsoft.Resources/subscriptions/resourceGroups/read", + "Microsoft.Resources/subscriptions/resourceGroups/resources/read", + "Microsoft.Insights/alertRules/*", + "Microsoft.Support/*" + ], + "AssignableScopes": ["/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + }' + - name: Create a role from a file containing a JSON description. + text: > + az role definition create --role-definition ad-role.json """ helps['ad'] = """ type: group diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_params.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_params.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_params.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_params.py 2018-01-12 18:25:22.000000000 +0100 @@ -4,70 +4,108 @@ # -------------------------------------------------------------------------------------------- # pylint: disable=line-too-long -from azure.cli.core.commands import CliArgumentType -from azure.cli.core.commands import register_cli_argument -from azure.cli.core.commands.parameters import enum_choice_list -from .custom import get_role_definition_name_completion_list -from ._validators import validate_group, validate_member_id, validate_cert, VARIANT_GROUP_ID_ARGS - -register_cli_argument('ad app', 'app_id', help='application id') -register_cli_argument('ad app', 'application_object_id', options_list=('--object-id',)) -register_cli_argument('ad app', 'display_name', help='the display name of the application') -register_cli_argument('ad app', 'homepage', help='the url where users can sign in and use your app.') -register_cli_argument('ad app', 'identifier', options_list=('--id',), help='identifier uri, application id, or object id') -register_cli_argument('ad app', 'identifier_uris', nargs='+', help='space separated unique URIs that Azure AD can use for this app.') -register_cli_argument('ad app', 'reply_urls', nargs='+', help='space separated URIs to which Azure AD will redirect in response to an OAuth 2.0 request. The value does not need to be a physical endpoint, but must be a valid URI.') -register_cli_argument('ad app', 'start_date', help="Date or datetime at which credentials become valid(e.g. '2017-01-01T01:00:00+00:00' or '2017-01-01'). Default value is current time") -register_cli_argument('ad app', 'end_date', help="Date or datetime after which credentials expire(e.g. '2017-12-31T11:59:59+00:00' or '2017-12-31'). Default value is one year after current time") -register_cli_argument('ad app', 'available_to_other_tenants', action='store_true', help='the application can be used from any Azure AD tenants') -register_cli_argument('ad app', 'key_value', help='the value for the key credentials associated with the application') -# TODO: Update these with **enum_choice_list(...) when SDK supports proper enums -register_cli_argument('ad app', 'key_type', default='AsymmetricX509Cert', help='the type of the key credentials associated with the application', **enum_choice_list(['AsymmetricX509Cert', 'Password', 'Symmetric'])) -register_cli_argument('ad app', 'key_usage', default='Verify', help='the usage of the key credentials associated with the application.', **enum_choice_list(['Sign', 'Verify'])) - -name_arg_type = CliArgumentType(options_list=('--name', '-n'), metavar='NAME') - -register_cli_argument('ad sp', 'identifier', options_list=('--id',), help='service principal name, or object id') -register_cli_argument('ad sp create', 'identifier', options_list=('--id',), help='identifier uri, application id, or object id of the associated application') -register_cli_argument('ad sp create-for-rbac', 'scopes', nargs='+') -register_cli_argument('ad sp create-for-rbac', 'role', completer=get_role_definition_name_completion_list) -register_cli_argument('ad sp create-for-rbac', 'skip_assignment', action='store_true', help='do not create default assignment') -register_cli_argument('ad sp create-for-rbac', 'show_auth_for_sdk', options_list='--sdk-auth', action='store_true', help='output result in compatible with Azure SDK auth file') - -for item in ['create-for-rbac', 'reset-credentials']: - register_cli_argument('ad sp {}'.format(item), 'name', name_arg_type) - register_cli_argument('ad sp {}'.format(item), 'cert', arg_group='Credential', validator=validate_cert) - register_cli_argument('ad sp {}'.format(item), 'password', options_list=('--password', '-p'), arg_group='Credential') - register_cli_argument('ad sp {}'.format(item), 'years', type=int, default=None, arg_group='Credential') - register_cli_argument('ad sp {}'.format(item), 'create_cert', action='store_true', arg_group='Credential') - register_cli_argument('ad sp {}'.format(item), 'keyvault', arg_group='Credential') - -register_cli_argument('ad', 'display_name', help='object\'s display name or its prefix') -register_cli_argument('ad', 'identifier_uri', help='graph application identifier, must be in uri format') -register_cli_argument('ad', 'spn', help='service principal name') -register_cli_argument('ad', 'upn', help='user principal name, e.g. [email protected]') -register_cli_argument('ad', 'query_filter', options_list=('--filter',), help='OData filter') -register_cli_argument('ad user', 'mail_nickname', help='mail alias. Defaults to user principal name') -register_cli_argument('ad user', 'force_change_password_next_login', action='store_true') - -group_help_msg = "group's object id or display name(prefix also works if there is a unique match)" -for arg in VARIANT_GROUP_ID_ARGS: - register_cli_argument('ad group', arg, options_list=('--group', '-g'), validator=validate_group, help=group_help_msg) - -register_cli_argument('ad group get-member-groups', 'security_enabled_only', action='store_true', default=False, required=False) -member_id_help_msg = 'The object ID of the contact, group, user, or service principal' -register_cli_argument('ad group member add', 'url', options_list='--member-id', validator=validate_member_id, help=member_id_help_msg) -register_cli_argument('ad group member', 'member_object_id', options_list='--member-id', help=member_id_help_msg) - -register_cli_argument('role', 'scope', help='scope at which the role assignment or definition applies to, e.g., /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333, /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333/resourceGroups/myGroup, or /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333/resourceGroups/myGroup/providers/Microsoft.Compute/virtualMachines/myVM') -register_cli_argument('role assignment', 'role_assignment_name', options_list=('--name', '-n')) -register_cli_argument('role assignment', 'role', help='role name or id', completer=get_role_definition_name_completion_list) -register_cli_argument('role assignment', 'show_all', options_list=('--all',), action='store_true', help='show all assignments under the current subscription') -register_cli_argument('role assignment', 'include_inherited', action='store_true', help='include assignments applied on parent scopes') -register_cli_argument('role assignment', 'assignee', help='represent a user, group, or service principal. supported format: object id, user sign-in name, or service principal name') -register_cli_argument('role assignment', 'ids', nargs='+', help='space separated role assignment ids') -register_cli_argument('role definition', 'role_definition_id', options_list=('--name', '-n'), help='the role definition name') -register_cli_argument('role', 'resource_group_name', options_list=('--resource-group', '-g'), help='use it only if the role or assignment was added at the level of a resource group') -register_cli_argument('role definition', 'custom_role_only', action='store_true', help='custom roles only(vs. build-in ones)') -register_cli_argument('role definition', 'role_definition', help="json formatted content which defines the new role.") -register_cli_argument('role definition', 'name', arg_type=name_arg_type, completer=get_role_definition_name_completion_list, help="the role's name") + +from knack.arguments import CLIArgumentType + +from azure.cli.core.commands.parameters import get_enum_type, get_three_state_flag + +from azure.cli.command_modules.role._completers import get_role_definition_name_completion_list +from azure.cli.command_modules.role._validators import validate_group, validate_member_id, validate_cert, VARIANT_GROUP_ID_ARGS + + +name_arg_type = CLIArgumentType(options_list=('--name', '-n'), metavar='NAME') + + +# pylint: disable=too-many-statements +def load_arguments(self, _): + with self.argument_context('ad app') as c: + c.argument('app_id', help='application id') + c.argument('application_object_id', options_list=('--object-id',)) + c.argument('display_name', help='the display name of the application') + c.argument('homepage', help='the url where users can sign in and use your app.') + c.argument('identifier', options_list=['--id'], help='identifier uri, application id, or object id') + c.argument('identifier_uris', nargs='+', help='space separated unique URIs that Azure AD can use for this app.') + c.argument('reply_urls', nargs='+', help='space separated URIs to which Azure AD will redirect in response to an OAuth 2.0 request. The value does not need to be a physical endpoint, but must be a valid URI.') + c.argument('start_date', help="Date or datetime at which credentials become valid(e.g. '2017-01-01T01:00:00+00:00' or '2017-01-01'). Default value is current time") + c.argument('end_date', help="Date or datetime after which credentials expire(e.g. '2017-12-31T11:59:59+00:00' or '2017-12-31'). Default value is one year after current time") + c.argument('available_to_other_tenants', help='the application can be used from any Azure AD tenants', arg_type=get_three_state_flag()) + c.argument('key_value', help='the value for the key credentials associated with the application') + # TODO: Update these with **enum_choice_list(...) when SDK supports proper enums + c.argument('key_type', help='the type of the key credentials associated with the application', arg_type=get_enum_type(['AsymmetricX509Cert', 'Password', 'Symmetric'], default='AsymmetricX509Cert')) + c.argument('key_usage', help='the usage of the key credentials associated with the application.', arg_type=get_enum_type(['Sign', 'Verify'], default='Verify')) + + with self.argument_context('ad sp') as c: + c.argument('identifier', options_list=['--id'], help='service principal name, or object id') + + with self.argument_context('ad sp create') as c: + c.argument('identifier', options_list=['--id'], help='identifier uri, application id, or object id of the associated application') + + with self.argument_context('ad sp create-for-rbac') as c: + c.argument('scopes', nargs='+') + c.argument('role', completer=get_role_definition_name_completion_list) + c.argument('skip_assignment', arg_type=get_three_state_flag(), help='do not create default assignment') + c.argument('show_auth_for_sdk', options_list='--sdk-auth', help='output result in compatible with Azure SDK auth file', arg_type=get_three_state_flag()) + + for item in ['create-for-rbac', 'reset-credentials']: + with self.argument_context('ad sp {}'.format(item)) as c: + c.argument('name', name_arg_type) + c.argument('cert', arg_group='Credential', validator=validate_cert) + c.argument('password', options_list=['--password', '-p'], arg_group='Credential') + c.argument('years', type=int, default=None, arg_group='Credential') + c.argument('create_cert', action='store_true', arg_group='Credential') + c.argument('keyvault', arg_group='Credential') + c.argument('append', action='store_true', help='Append the new credential instead of overwriting.') + + with self.argument_context('ad') as c: + c.argument('display_name', help='object\'s display name or its prefix') + c.argument('identifier_uri', help='graph application identifier, must be in uri format') + c.argument('spn', help='service principal name') + c.argument('upn', help='user principal name, e.g. [email protected]') + c.argument('query_filter', options_list=['--filter'], help='OData filter') + + with self.argument_context('ad user') as c: + c.argument('mail_nickname', help='mail alias. Defaults to user principal name') + c.argument('force_change_password_next_login', arg_type=get_three_state_flag()) + + group_help_msg = "group's object id or display name(prefix also works if there is a unique match)" + with self.argument_context('ad group') as c: + for arg in VARIANT_GROUP_ID_ARGS: + c.argument(arg, options_list=['--group', '-g'], validator=validate_group, help=group_help_msg) + + with self.argument_context('ad group show') as c: + c.extra('cmd') + + member_id_help_msg = 'The object ID of the contact, group, user, or service principal' + with self.argument_context('ad group get-member-groups') as c: + c.argument('security_enabled_only', arg_type=get_three_state_flag(), default=False, required=False) + c.extra('cmd') + + with self.argument_context('ad group member add') as c: + c.argument('url', options_list='--member-id', validator=validate_member_id, help=member_id_help_msg) + + for item in ['member add', 'member check', 'member list', 'member remove', 'delete']: + with self.argument_context('ad group {}'.format(item)) as c: + c.extra('cmd') + + with self.argument_context('ad group member') as c: + c.argument('member_object_id', options_list='--member-id', help=member_id_help_msg) + + with self.argument_context('role') as c: + c.argument('scope', help='scope at which the role assignment or definition applies to, e.g., /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333, /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333/resourceGroups/myGroup, or /subscriptions/0b1f6471-1bf0-4dda-aec3-111122223333/resourceGroups/myGroup/providers/Microsoft.Compute/virtualMachines/myVM') + c.argument('resource_group_name', options_list=['--resource-group', '-g'], help='use it only if the role or assignment was added at the level of a resource group') + + with self.argument_context('role assignment') as c: + c.argument('role_assignment_name', options_list=['--name', '-n']) + c.argument('role', help='role name or id', completer=get_role_definition_name_completion_list) + c.argument('show_all', options_list=['--all'], action='store_true', help='show all assignments under the current subscription') + c.argument('include_inherited', action='store_true', help='include assignments applied on parent scopes') + c.argument('assignee', help='represent a user, group, or service principal. supported format: object id, user sign-in name, or service principal name') + c.argument('assignee_object_id', help="assignee's graph object id, such as the 'principal id' from a managed service identity. Use this instead of '--assignee' to bypass graph permission issues") + c.argument('ids', nargs='+', help='space separated role assignment ids') + c.argument('include_classic_administrators', arg_type=get_three_state_flag(), help='list default role assignments for subscription classic administrators, aka co-admins') + + with self.argument_context('role definition') as c: + c.argument('role_definition_id', options_list=['--name', '-n'], help='the role definition name') + c.argument('custom_role_only', arg_type=get_three_state_flag(), help='custom roles only(vs. build-in ones)') + c.argument('role_definition', help="json formatted content which defines the new role.") + c.argument('name', arg_type=name_arg_type, completer=get_role_definition_name_completion_list, help="the role's name") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_validators.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_validators.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/_validators.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/_validators.py 2018-01-12 18:25:22.000000000 +0100 @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- import uuid -from azure.cli.core.util import CLIError +from knack.util import CLIError from ._client_factory import _graph_client_factory VARIANT_GROUP_ID_ARGS = ['object_id', 'group_id', 'group_object_id'] @@ -17,7 +17,7 @@ try: uuid.UUID(value) except ValueError: - client = _graph_client_factory() + client = _graph_client_factory(namespace.cmd.cli_ctx) sub_filters = [] sub_filters.append("startswith(displayName,'{}')".format(value)) sub_filters.append("displayName eq '{}'".format(value)) @@ -32,12 +32,13 @@ def validate_member_id(namespace): - from azure.cli.core._profile import Profile, CLOUD + from azure.cli.core._profile import Profile + cli_ctx = namespace.cmd.cli_ctx try: uuid.UUID(namespace.url) - profile = Profile() + profile = Profile(cli_ctx=cli_ctx) _, _, tenant_id = profile.get_login_credentials() - graph_url = CLOUD.endpoints.active_directory_graph_resource_id + graph_url = cli_ctx.cloud.endpoints.active_directory_graph_resource_id namespace.url = '{}{}/directoryObjects/{}'.format(graph_url, tenant_id, namespace.url) except ValueError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/commands.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/commands.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/commands.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/commands.py 2018-01-12 18:25:22.000000000 +0100 @@ -6,8 +6,9 @@ from collections import OrderedDict -from azure.cli.core.commands import cli_command -from azure.cli.core.profiles import supported_api_version, PROFILE_TYPE +from azure.cli.core.profiles import PROFILE_TYPE +from azure.cli.core.commands import CliCommandType + from azure.cli.core.util import empty_on_404 from ._client_factory import (_auth_client_factory, _graph_client_factory) @@ -29,100 +30,83 @@ '#RoleDefinitionsOperations.{}'.format(operation_name) -def get_role_definitions(_): - return _auth_client_factory().role_definitions +def get_role_definitions(cli_ctx, _): + return _auth_client_factory(cli_ctx, ).role_definitions + + +def get_graph_client_applications(cli_ctx, _): + return _graph_client_factory(cli_ctx).applications + + +def get_graph_client_service_principals(cli_ctx, _): + return _graph_client_factory(cli_ctx).service_principals + + +def get_graph_client_users(cli_ctx, _): + return _graph_client_factory(cli_ctx).users + + +def get_graph_client_groups(cli_ctx, _): + return _graph_client_factory(cli_ctx).groups + + +# pylint: disable=line-too-long +def load_command_table(self, _): + + role_users_sdk = CliCommandType( + operations_tmpl='azure.graphrbac.operations.users_operations#UsersOperations.{}', + client_factory=get_graph_client_users + ) + + role_group_sdk = CliCommandType( + operations_tmpl='azure.graphrbac.operations.groups_operations#GroupsOperations.{}', + client_factory=get_graph_client_groups + ) + with self.command_group('role definition') as g: + g.custom_command('list', 'list_role_definitions', table_transformer=transform_definition_list) + g.custom_command('delete', 'delete_role_definition') + g.custom_command('create', 'create_role_definition') + g.custom_command('update', 'update_role_definition') -def get_graph_client_applications(_): - return _graph_client_factory().applications + with self.command_group('role assignment') as g: + g.custom_command('delete', 'delete_role_assignments') + g.custom_command('list', 'list_role_assignments', table_transformer=transform_assignment_list) + g.custom_command('create', 'create_role_assignment') + with self.command_group('ad app', client_factory=get_graph_client_applications, resource_type=PROFILE_TYPE, min_api='2017-03-10') as g: + g.custom_command('create', 'create_application') + g.custom_command('delete', 'delete_application') + g.custom_command('list', 'list_apps') + g.custom_command('show', 'show_application', exception_handler=empty_on_404) + g.custom_command('update', 'update_application') -def get_graph_client_service_principals(_): - return _graph_client_factory().service_principals - - -def get_graph_client_users(_): - return _graph_client_factory().users - - -def get_graph_client_groups(_): - return _graph_client_factory().groups - - -cli_command(__name__, 'role definition list', - 'azure.cli.command_modules.role.custom#list_role_definitions', - table_transformer=transform_definition_list) -cli_command(__name__, 'role definition delete', - 'azure.cli.command_modules.role.custom#delete_role_definition') -cli_command(__name__, 'role definition create', - 'azure.cli.command_modules.role.custom#create_role_definition') -cli_command(__name__, 'role definition update', - 'azure.cli.command_modules.role.custom#update_role_definition') - -cli_command(__name__, 'role assignment delete', - 'azure.cli.command_modules.role.custom#delete_role_assignments') -cli_command(__name__, 'role assignment list', - 'azure.cli.command_modules.role.custom#list_role_assignments', - table_transformer=transform_assignment_list) -cli_command(__name__, 'role assignment create', - 'azure.cli.command_modules.role.custom#create_role_assignment') - -if not supported_api_version(PROFILE_TYPE, max_api='2017-03-09-profile'): - cli_command(__name__, 'ad app create', 'azure.cli.command_modules.role.custom#create_application', - get_graph_client_applications) - cli_command(__name__, 'ad app delete', 'azure.cli.command_modules.role.custom#delete_application', - get_graph_client_applications) - cli_command(__name__, 'ad app list', 'azure.cli.command_modules.role.custom#list_apps', - get_graph_client_applications) - cli_command(__name__, 'ad app show', 'azure.cli.command_modules.role.custom#show_application', - get_graph_client_applications, exception_handler=empty_on_404) - cli_command(__name__, 'ad app update', 'azure.cli.command_modules.role.custom#update_application', - get_graph_client_applications) - - cli_command(__name__, 'ad sp create', - 'azure.cli.command_modules.role.custom#create_service_principal') - cli_command(__name__, 'ad sp delete', - 'azure.cli.command_modules.role.custom#delete_service_principal') - cli_command(__name__, 'ad sp list', 'azure.cli.command_modules.role.custom#list_sps', - get_graph_client_service_principals) - cli_command(__name__, 'ad sp show', 'azure.cli.command_modules.role.custom#show_service_principal', - get_graph_client_service_principals, exception_handler=empty_on_404) + with self.command_group('ad sp', resource_type=PROFILE_TYPE, min_api='2017-03-10') as g: + g.custom_command('create', 'create_service_principal') + g.custom_command('delete', 'delete_service_principal') + g.custom_command('list', 'list_sps', client_factory=get_graph_client_service_principals) + g.custom_command('show', 'show_service_principal', client_factory=get_graph_client_service_principals, exception_handler=empty_on_404) # RBAC related - cli_command(__name__, 'ad sp create-for-rbac', - 'azure.cli.command_modules.role.custom#create_service_principal_for_rbac') - cli_command(__name__, 'ad sp reset-credentials', - 'azure.cli.command_modules.role.custom#reset_service_principal_credential') - - cli_command(__name__, 'ad user delete', - 'azure.graphrbac.operations.users_operations#UsersOperations.delete', - get_graph_client_users) - cli_command(__name__, 'ad user show', - 'azure.graphrbac.operations.users_operations#UsersOperations.get', - get_graph_client_users, - exception_handler=empty_on_404) - cli_command(__name__, 'ad user list', 'azure.cli.command_modules.role.custom#list_users', - get_graph_client_users) - cli_command(__name__, 'ad user create', 'azure.cli.command_modules.role.custom#create_user', - get_graph_client_users) - - group_path = 'azure.graphrbac.operations.groups_operations#GroupsOperations.{}' - cli_command(__name__, 'ad group create', group_path.format('create'), get_graph_client_groups) - cli_command(__name__, 'ad group delete', group_path.format('delete'), get_graph_client_groups) - cli_command(__name__, 'ad group show', group_path.format('get'), get_graph_client_groups, - exception_handler=empty_on_404) - cli_command(__name__, 'ad group list', - 'azure.cli.command_modules.role.custom#list_groups', - get_graph_client_groups) - - cli_command(__name__, 'ad group get-member-groups', group_path.format('get_member_groups'), - get_graph_client_groups) - - cli_command(__name__, 'ad group member list', group_path.format('get_group_members'), - get_graph_client_groups) - cli_command(__name__, 'ad group member add', group_path.format('add_member'), - get_graph_client_groups) - cli_command(__name__, 'ad group member remove', group_path.format('remove_member'), - get_graph_client_groups) - cli_command(__name__, 'ad group member check', group_path.format('is_member_of'), - get_graph_client_groups) + with self.command_group('ad sp') as g: + g.custom_command('create-for-rbac', 'create_service_principal_for_rbac') + g.custom_command('reset-credentials', 'reset_service_principal_credential') + + with self.command_group('ad user', role_users_sdk) as g: + g.command('delete', 'delete') + g.command('show', 'get', exception_handler=empty_on_404) + g.custom_command('list', 'list_users', client_factory=get_graph_client_users) + g.custom_command('create', 'create_user', client_factory=get_graph_client_users, doc_string_source='azure.graphrbac.models#UserCreateParameters') + + with self.command_group('ad group', role_group_sdk) as g: + g.command('create', 'create') + g.command('delete', 'delete') + g.command('show', 'get', exception_handler=empty_on_404) + g.command('get-member-groups', 'get_member_groups') + g.custom_command('list', 'list_groups', client_factory=get_graph_client_groups) + + with self.command_group('ad group member', role_group_sdk) as g: + g.command('list', 'get_group_members') + g.command('add', 'add_member') + g.command('remove', 'remove_member') + g.command('check', 'is_member_of') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure/cli/command_modules/role/custom.py new/azure-cli-role-2.0.17/azure/cli/command_modules/role/custom.py --- old/azure-cli-role-2.0.12/azure/cli/command_modules/role/custom.py 2017-09-23 01:47:00.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure/cli/command_modules/role/custom.py 2018-01-12 18:25:22.000000000 +0100 @@ -12,8 +12,13 @@ from dateutil.relativedelta import relativedelta import dateutil.parser -from azure.cli.core.util import CLIError, todict, get_file_json, shell_safe_json_parse -from azure.cli.core import get_az_logger +from knack.log import get_logger +from knack.util import CLIError, todict + +from msrestazure.azure_exceptions import CloudError +from azure.graphrbac.models.graph_error import GraphErrorException + +from azure.cli.core.util import get_file_json, shell_safe_json_parse from azure.mgmt.authorization.models import (RoleAssignmentProperties, Permission, RoleDefinition, RoleDefinitionProperties) @@ -28,34 +33,31 @@ from ._client_factory import _auth_client_factory, _graph_client_factory -logger = get_az_logger(__name__) +logger = get_logger(__name__) _CUSTOM_RULE = 'CustomRole' +# pylint: disable=too-many-lines + -def list_role_definitions(name=None, resource_group_name=None, scope=None, +def list_role_definitions(cmd, name=None, resource_group_name=None, scope=None, custom_role_only=False): - definitions_client = _auth_client_factory(scope).role_definitions + definitions_client = _auth_client_factory(cmd.cli_ctx, scope).role_definitions scope = _build_role_scope(resource_group_name, scope, definitions_client.config.subscription_id) return _search_role_definitions(definitions_client, name, scope, custom_role_only) -def get_role_definition_name_completion_list(prefix, **kwargs): # pylint: disable=unused-argument - definitions = list_role_definitions() - return [x.properties.role_name for x in list(definitions)] - - -def create_role_definition(role_definition): - return _create_update_role_definition(role_definition, for_update=False) +def create_role_definition(cmd, role_definition): + return _create_update_role_definition(cmd.cli_ctx, role_definition, for_update=False) -def update_role_definition(role_definition): - return _create_update_role_definition(role_definition, for_update=True) +def update_role_definition(cmd, role_definition): + return _create_update_role_definition(cmd.cli_ctx, role_definition, for_update=True) -def _create_update_role_definition(role_definition, for_update): - definitions_client = _auth_client_factory().role_definitions +def _create_update_role_definition(cli_ctx, role_definition, for_update): + definitions_client = _auth_client_factory(cli_ctx).role_definitions if os.path.exists(role_definition): role_definition = get_file_json(role_definition) else: @@ -82,7 +84,7 @@ role_name = matched[0].properties.role_name role_id = matched[0].name else: - role_id = uuid.uuid4() + role_id = _gen_guid() if not for_update and 'assignableScopes' not in role_definition: raise CLIError("please provide 'assignableScopes'") @@ -102,9 +104,9 @@ role_definition=definition) -def delete_role_definition(name, resource_group_name=None, scope=None, +def delete_role_definition(cmd, name, resource_group_name=None, scope=None, custom_role_only=False): - definitions_client = _auth_client_factory(scope).role_definitions + definitions_client = _auth_client_factory(cmd.cli_ctx, scope).role_definitions scope = _build_role_scope(resource_group_name, scope, definitions_client.config.subscription_id) roles = _search_role_definitions(definitions_client, name, scope, custom_role_only) @@ -121,13 +123,16 @@ return roles -def create_role_assignment(role, assignee, resource_group_name=None, scope=None): - return _create_role_assignment(role, assignee, resource_group_name, scope) +def create_role_assignment(cmd, role, assignee=None, assignee_object_id=None, resource_group_name=None, scope=None): + if bool(assignee) == bool(assignee_object_id): + raise CLIError('usage error: --assignee STRING | --assignee-object-id GUID') + return _create_role_assignment(cmd.cli_ctx, role, assignee or assignee_object_id, + resource_group_name, scope, resolve_assignee=(not assignee_object_id)) -def _create_role_assignment(role, assignee, resource_group_name=None, scope=None, +def _create_role_assignment(cli_ctx, role, assignee, resource_group_name=None, scope=None, resolve_assignee=True): - factory = _auth_client_factory(scope) + factory = _auth_client_factory(cli_ctx, scope) assignments_client = factory.role_assignments definitions_client = factory.role_definitions @@ -135,23 +140,23 @@ assignments_client.config.subscription_id) role_id = _resolve_role_id(role, scope, definitions_client) - object_id = _resolve_object_id(assignee) if resolve_assignee else assignee + object_id = _resolve_object_id(cli_ctx, assignee) if resolve_assignee else assignee properties = RoleAssignmentProperties(role_id, object_id) - assignment_name = uuid.uuid4() + assignment_name = _gen_guid() custom_headers = None return assignments_client.create(scope, assignment_name, properties, custom_headers=custom_headers) -def list_role_assignments(assignee=None, role=None, resource_group_name=None, +def list_role_assignments(cmd, assignee=None, role=None, resource_group_name=None, scope=None, include_inherited=False, - show_all=False, include_groups=False): + show_all=False, include_groups=False, include_classic_administrators=False): ''' :param include_groups: include extra assignments to the groups of which the user is a member(transitively). Supported only for a user principal. ''' - graph_client = _graph_client_factory() - factory = _auth_client_factory(scope) + graph_client = _graph_client_factory(cmd.cli_ctx) + factory = _auth_client_factory(cmd.cli_ctx, scope) assignments_client = factory.role_assignments definitions_client = factory.role_definitions @@ -163,38 +168,84 @@ scope = _build_role_scope(resource_group_name, scope, definitions_client.config.subscription_id) - assignments = _search_role_assignments(assignments_client, definitions_client, + assignments = _search_role_assignments(cmd.cli_ctx, assignments_client, definitions_client, scope, assignee, role, include_inherited, include_groups) - if not assignments: - return [] - - # fill in logic names to get things understandable. - # it's possible that associated roles and principals were deleted, and we just do nothing. + results = todict(assignments) if assignments else [] + if include_classic_administrators: + results += _backfill_assignments_for_co_admins(cmd.cli_ctx, factory, assignee) - results = todict(assignments) + if not results: + return [] - # fill in role names + # 1. fill in logic names to get things understandable. + # (it's possible that associated roles and principals were deleted, and we just do nothing.) + # 2. fill in role names role_defs = list(definitions_client.list( scope=scope or ('/subscriptions/' + definitions_client.config.subscription_id))) role_dics = {i.id: i.properties.role_name for i in role_defs} for i in results: - i['properties']['roleDefinitionName'] = role_dics.get(i['properties']['roleDefinitionId'], - None) + if role_dics.get(i['properties']['roleDefinitionId']): + i['properties']['roleDefinitionName'] = role_dics[i['properties']['roleDefinitionId']] # fill in principal names - principal_ids = set(i['properties']['principalId'] for i in results) + principal_ids = set(i['properties']['principalId'] for i in results if i['properties']['principalId']) if principal_ids: - principals = _get_object_stubs(graph_client, principal_ids) - principal_dics = {i.object_id: _get_displayable_name(i) for i in principals} - for i in results: - i['properties']['principalName'] = principal_dics.get(i['properties']['principalId'], - None) + try: + principals = _get_object_stubs(graph_client, principal_ids) + principal_dics = {i.object_id: _get_displayable_name(i) for i in principals} + + for i in [r for r in results if not r['properties'].get('principalName')]: + i['properties']['principalName'] = '' + if principal_dics.get(i['properties']['principalId']): + i['properties']['principalName'] = principal_dics[i['properties']['principalId']] + except (CloudError, GraphErrorException) as ex: + # failure on resolving principal due to graph permission should not fail the whole thing + logger.info("Failed to resolve graph object information per error '%s'", ex) return results +def _backfill_assignments_for_co_admins(cli_ctx, auth_client, assignee=None): + co_admins = auth_client.classic_administrators.list('2015-06-01') # known swagger bug on api-version handling + co_admins = [x for x in co_admins if x.properties.email_address] + graph_client = _graph_client_factory(cli_ctx) + if assignee: # apply assignee filter if applicable + if _is_guid(assignee): + result = _get_object_stubs(graph_client, [assignee]) + if not result: + return [] + assignee = _get_displayable_name(result[0]).lower() + + co_admins = [x for x in co_admins if assignee == x.properties.email_address.lower()] + + if not co_admins: + return [] + + result, users = [], [] + for i in range(0, len(co_admins), 10): # graph allows up to 10 query filters, so split into chunks here + upn_queries = ["userPrincipalName eq '{}'".format(x.properties.email_address) for x in co_admins[i:i + 10]] + temp = list(list_users(graph_client.users, query_filter=' or '.join(upn_queries))) + users += temp + upns = {u.user_principal_name: u.object_id for u in users} + for admin in co_admins: + na_text = 'NA(classic admins)' + email = admin.properties.email_address + result.append({ + 'id': na_text, + 'name': na_text, + 'properties': { + 'principalId': upns.get(email), + 'principalName': email, + 'roleDefinitionName': admin.properties.role, + 'roleDefinitionId': 'NA(classic admin role)', + 'scope': '/subscriptions/' + auth_client.config.subscription_id + } + }) + return result + + def _get_displayable_name(graph_object): if graph_object.user_principal_name: return graph_object.user_principal_name @@ -203,9 +254,9 @@ return graph_object.display_name or '' -def delete_role_assignments(ids=None, assignee=None, role=None, +def delete_role_assignments(cmd, ids=None, assignee=None, role=None, resource_group_name=None, scope=None, include_inherited=False): - factory = _auth_client_factory(scope) + factory = _auth_client_factory(cmd.cli_ctx, scope) assignments_client = factory.role_assignments definitions_client = factory.role_definitions ids = ids or [] @@ -218,7 +269,7 @@ scope = _build_role_scope(resource_group_name, scope, assignments_client.config.subscription_id) - assignments = _search_role_assignments(assignments_client, definitions_client, + assignments = _search_role_assignments(cmd.cli_ctx, assignments_client, definitions_client, scope, assignee, role, include_inherited, include_groups=False) @@ -229,11 +280,11 @@ raise CLIError('No matched assignments were found to delete') -def _search_role_assignments(assignments_client, definitions_client, +def _search_role_assignments(cli_ctx, assignments_client, definitions_client, scope, assignee, role, include_inherited, include_groups): assignee_object_id = None if assignee: - assignee_object_id = _resolve_object_id(assignee) + assignee_object_id = _resolve_object_id(cli_ctx, assignee, fallback_to_object_id=True) # combining filters is unsupported, so we pick the best, and do limited maunal filtering if assignee_object_id: @@ -280,12 +331,9 @@ role, re.I): role_id = role else: - try: - uuid.UUID(role) + if _is_guid(role): role_id = '/subscriptions/{}/providers/Microsoft.Authorization/roleDefinitions/{}'.format( definitions_client.config.subscription_id, role) - except ValueError: - pass if not role_id: # retrieve role id role_defs = list(definitions_client.list(scope, "roleName eq '{}'".format(role))) if not role_defs: @@ -350,9 +398,6 @@ return client.create(param) -create_user.__doc__ = UserCreateParameters.__doc__ - - def list_groups(client, display_name=None, query_filter=None): ''' list groups in the directory @@ -379,7 +424,15 @@ reply_urls=reply_urls, key_credentials=key_creds, password_credentials=password_creds) - return client.create(app_create_param) + + try: + return client.create(app_create_param) + except GraphErrorException as ex: + if 'insufficient privileges' in str(ex).lower(): + link = 'https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal' # pylint: disable=line-too-long + raise CLIError("Directory permission is needed for the current user to register the application. " + "For how to configure, please refer '{}'. Original error: {}".format(link, ex)) + raise def update_application(client, identifier, display_name=None, homepage=None, @@ -411,11 +464,10 @@ def _resolve_application(client, identifier): result = list(client.list(filter="identifierUris/any(s:s eq '{}')".format(identifier))) if not result: - try: - uuid.UUID(identifier) + if _is_guid(identifier): # it is either app id or object id, let us verify result = list(client.list(filter="appId eq '{}'".format(identifier))) - except ValueError: + else: raise CLIError("Application '{}' doesn't exist".format(identifier)) return result[0].object_id if result else identifier @@ -442,26 +494,25 @@ password_creds = None key_creds = None if password: - password_creds = [PasswordCredential(start_date, end_date, str(uuid.uuid4()), password)] + password_creds = [PasswordCredential(start_date, end_date, str(_gen_guid()), password)] elif key_value: - key_creds = [KeyCredential(start_date, end_date, key_value, str(uuid.uuid4()), + key_creds = [KeyCredential(start_date, end_date, key_value, str(_gen_guid()), key_usage, key_type)] return (password_creds, key_creds) -def create_service_principal(identifier): - return _create_service_principal(identifier) +def create_service_principal(cmd, identifier): + return _create_service_principal(cmd.cli_ctx, identifier) -def _create_service_principal(identifier, resolve_app=True): - client = _graph_client_factory() +def _create_service_principal(cli_ctx, identifier, resolve_app=True): + client = _graph_client_factory(cli_ctx) if resolve_app: - try: - uuid.UUID(identifier) + if _is_guid(identifier): result = list(client.applications.list(filter="appId eq '{}'".format(identifier))) - except ValueError: + else: result = list(client.applications.list( filter="identifierUris/any(s:s eq '{}')".format(identifier))) @@ -479,8 +530,8 @@ return client.get(object_id) -def delete_service_principal(identifier): - client = _graph_client_factory() +def delete_service_principal(cmd, identifier): + client = _graph_client_factory(cmd.cli_ctx) sp = client.service_principals.get(_resolve_service_principal(client.service_principals, identifier)) app_object_id = None @@ -491,10 +542,10 @@ if result: app_object_id = result[0].object_id - assignments = list_role_assignments(assignee=identifier, show_all=True) + assignments = list_role_assignments(cmd, assignee=identifier, show_all=True) if assignments: logger.warning('Removing role assignments') - delete_role_assignments([a['id'] for a in assignments]) + delete_role_assignments(cmd, [a['id'] for a in assignments]) if app_object_id: # delete the application, and AAD service will automatically clean up the SP client.applications.delete(app_object_id) @@ -507,19 +558,18 @@ result = list(client.list(filter="servicePrincipalNames/any(c:c eq '{}')".format(identifier))) if result: return result[0].object_id - try: - uuid.UUID(identifier) + if _is_guid(identifier): return identifier # assume an object id - except ValueError: + else: raise CLIError("service principal '{}' doesn't exist".format(identifier)) -def _process_service_principal_creds(years, app_start_date, app_end_date, cert, create_cert, +def _process_service_principal_creds(cli_ctx, years, app_start_date, app_end_date, cert, create_cert, password, keyvault): if not any((cert, create_cert, password, keyvault)): # 1 - Simplest scenario. Use random password - return str(uuid.uuid4()), None, None, None, None + return str(_gen_guid()), None, None, None, None if password: # 2 - Password supplied -- no certs @@ -542,14 +592,12 @@ elif create_cert and keyvault: # 5 - Create self-signed cert in KeyVault public_cert_string, cert_file, cert_start_date, cert_end_date = \ - _create_self_signed_cert_with_keyvault( - years, keyvault, cert) + _create_self_signed_cert_with_keyvault(cli_ctx, years, keyvault, cert) elif keyvault: import base64 - from azure.cli.core._profile import CLOUD # 6 - Use existing cert from KeyVault - kv_client = _get_keyvault_client() - vault_base = 'https://{}{}/'.format(keyvault, CLOUD.suffixes.keyvault_dns) + kv_client = _get_keyvault_client(cli_ctx) + vault_base = 'https://{}{}/'.format(keyvault, cli_ctx.cloud.suffixes.keyvault_dns) cert_obj = kv_client.get_certificate(vault_base, cert, '') public_cert_string = base64.b64encode(cert_obj.cer).decode('utf-8') # pylint: disable=no-member cert_start_date = cert_obj.attributes.not_before # pylint: disable=no-member @@ -576,17 +624,18 @@ return (app_start_date, app_end_date, cert_start_date, cert_end_date) +# pylint: disable=inconsistent-return-statements def create_service_principal_for_rbac( # pylint:disable=too-many-statements,too-many-locals, too-many-branches - name=None, password=None, years=None, + cmd, name=None, password=None, years=None, create_cert=False, cert=None, scopes=None, role='Contributor', show_auth_for_sdk=None, skip_assignment=False, keyvault=None): import time import pytz - graph_client = _graph_client_factory() - role_client = _auth_client_factory().role_assignments + graph_client = _graph_client_factory(cmd.cli_ctx) + role_client = _auth_client_factory(cmd.cli_ctx).role_assignments scopes = scopes or ['/subscriptions/' + role_client.config.subscription_id] years = years or 1 sp_oid = None @@ -612,7 +661,7 @@ name = 'http://' + app_display_name # just a valid uri, no need to exist password, public_cert_string, cert_file, cert_start_date, cert_end_date = \ - _process_service_principal_creds(years, app_start_date, app_end_date, cert, create_cert, + _process_service_principal_creds(cmd.cli_ctx, years, app_start_date, app_end_date, cert, create_cert, password, keyvault) app_start_date, app_end_date, cert_start_date, cert_end_date = \ @@ -632,7 +681,7 @@ # retry till server replication is done for l in range(0, _RETRY_TIMES): try: - aad_sp = _create_service_principal(app_id, resolve_app=False) + aad_sp = _create_service_principal(cmd.cli_ctx, app_id, resolve_app=False) break except Exception as ex: # pylint: disable=broad-except if l < _RETRY_TIMES and ( @@ -652,7 +701,7 @@ for scope in scopes: for l in range(0, _RETRY_TIMES): try: - _create_role_assignment(role, sp_oid, None, scope, resolve_assignee=False) + _create_role_assignment(cmd.cli_ctx, role, sp_oid, None, scope, resolve_assignee=False) break except Exception as ex: if l < _RETRY_TIMES and ' does not exist in the directory ' in str(ex): @@ -671,7 +720,7 @@ if show_auth_for_sdk: import json from azure.cli.core._profile import Profile - profile = Profile() + profile = Profile(cli_ctx=cmd.cli_ctx) result = profile.get_sp_auth_info(scopes[0].split('/')[2] if scopes else None, app_id, password, cert_file) # sdk-auth file should be in json format all the time, hence the print @@ -693,12 +742,12 @@ return result -def _get_keyvault_client(): +def _get_keyvault_client(cli_ctx): from azure.cli.core._profile import Profile from azure.keyvault import KeyVaultClient, KeyVaultAuthentication def _get_token(server, resource, scope): # pylint: disable=unused-argument - return Profile().get_login_credentials(resource)[0]._token_retriever() # pylint: disable=protected-access + return Profile(cli_ctx=cli_ctx).get_login_credentials(resource)[0]._token_retriever() # pylint: disable=protected-access return KeyVaultClient(KeyVaultAuthentication(_get_token)) @@ -754,12 +803,11 @@ return (cert_string, creds_file, cert_start_date, cert_end_date) -def _create_self_signed_cert_with_keyvault(years, keyvault, keyvault_cert_name): # pylint: disable=too-many-locals - from azure.cli.core._profile import CLOUD +def _create_self_signed_cert_with_keyvault(cli_ctx, years, keyvault, keyvault_cert_name): # pylint: disable=too-many-locals import base64 import time - kv_client = _get_keyvault_client() + kv_client = _get_keyvault_client(cli_ctx) cert_policy = { 'issuer_parameters': { 'name': 'Self' @@ -794,7 +842,7 @@ 'validity_in_months': ((years * 12) + 1) } } - vault_base_url = 'https://{}{}/'.format(keyvault, CLOUD.suffixes.keyvault_dns) + vault_base_url = 'https://{}{}/'.format(keyvault, cli_ctx.cloud.suffixes.keyvault_dns) kv_client.create_certificate(vault_base_url, keyvault_cert_name, cert_policy) while kv_client.get_certificate_operation(vault_base_url, keyvault_cert_name).status != 'completed': # pylint: disable=no-member, line-too-long time.sleep(5) @@ -845,10 +893,10 @@ return stripped -def reset_service_principal_credential(name, password=None, create_cert=False, - cert=None, years=None, keyvault=None): +def reset_service_principal_credential(cmd, name, password=None, create_cert=False, + cert=None, years=None, keyvault=None, append=False): import pytz - client = _graph_client_factory() + client = _graph_client_factory(cmd.cli_ctx) # pylint: disable=no-member @@ -873,7 +921,7 @@ cert_file = None password, public_cert_string, cert_file, cert_start_date, cert_end_date = \ - _process_service_principal_creds(years, app_start_date, app_end_date, cert, create_cert, + _process_service_principal_creds(cmd.cli_ctx, years, app_start_date, app_end_date, cert, create_cert, password, keyvault) app_start_date, app_end_date, cert_start_date, cert_end_date = \ @@ -883,26 +931,28 @@ cert_creds = None if password: - app_creds = [ - PasswordCredential( - start_date=app_start_date, - end_date=app_end_date, - key_id=str(uuid.uuid4()), - value=password - ) - ] + app_creds = [] + if append: + app_creds = list(client.applications.list_password_credentials(app.object_id)) + app_creds.append(PasswordCredential( + start_date=app_start_date, + end_date=app_end_date, + key_id=str(_gen_guid()), + value=password + )) if public_cert_string: - cert_creds = [ - KeyCredential( - start_date=app_start_date, - end_date=app_end_date, - value=public_cert_string, - key_id=str(uuid.uuid4()), - usage='Verify', - type='AsymmetricX509Cert' - ) - ] + cert_creds = [] + if append: + cert_creds = list(client.applications.list_key_credentials(app.object_id)) + cert_creds.append(KeyCredential( + start_date=app_start_date, + end_date=app_end_date, + value=public_cert_string, + key_id=str(_gen_guid()), + usage='Verify', + type='AsymmetricX509Cert' + )) app_create_param = ApplicationUpdateParameters(password_credentials=app_creds, key_credentials=cert_creds) @@ -919,22 +969,41 @@ return result -def _resolve_object_id(assignee): - client = _graph_client_factory() +def _resolve_object_id(cli_ctx, assignee, fallback_to_object_id=False): + client = _graph_client_factory(cli_ctx) result = None - if assignee.find('@') >= 0: # looks like a user principal name - result = list(client.users.list(filter="userPrincipalName eq '{}'".format(assignee))) - if not result: - result = list(client.service_principals.list( - filter="servicePrincipalNames/any(c:c eq '{}')".format(assignee))) - if not result: # assume an object id, let us verify it - result = _get_object_stubs(client, [assignee]) + try: + if assignee.find('@') >= 0: # looks like a user principal name + result = list(client.users.list(filter="userPrincipalName eq '{}'".format(assignee))) + if not result: + result = list(client.service_principals.list( + filter="servicePrincipalNames/any(c:c eq '{}')".format(assignee))) + if not result: # assume an object id, let us verify it + result = _get_object_stubs(client, [assignee]) + + # 2+ matches should never happen, so we only check 'no match' here + if not result: + raise CLIError("No matches in graph database for '{}'".format(assignee)) + + return result[0].object_id + except (CloudError, GraphErrorException): + if fallback_to_object_id and _is_guid(assignee): + return assignee + raise + + +def _is_guid(guid): + try: + uuid.UUID(guid) + return True + except ValueError: + pass + return False - # 2+ matches should never happen, so we only check 'no match' here - if not result: - raise CLIError("No matches in graph database for '{}'".format(assignee)) - return result[0].object_id +# for injecting test seams to produce predicatable role assignment id for playback +def _gen_guid(): + return uuid.uuid4() def _get_object_stubs(graph_client, assignees): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure_cli_role.egg-info/PKG-INFO new/azure-cli-role-2.0.17/azure_cli_role.egg-info/PKG-INFO --- old/azure-cli-role-2.0.12/azure_cli_role.egg-info/PKG-INFO 2017-09-23 01:49:20.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure_cli_role.egg-info/PKG-INFO 2018-01-12 18:25:50.000000000 +0100 @@ -1,12 +1,11 @@ Metadata-Version: 1.1 Name: azure-cli-role -Version: 2.0.12 +Version: 2.0.17 Summary: Microsoft Azure Command-Line Tools Role Command Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation Author-email: [email protected] License: MIT -Description-Content-Type: UNKNOWN Description: Microsoft Azure CLI 'role' Command Module for Role-Based Access Control (RBAC) ============================================================================== @@ -20,6 +19,29 @@ Release History =============== + + 2.0.17 + ++++++ + * role assignment: expose --assignee-object-id to bypass graph query + + 2.0.16 + ++++++ + * Update for CLI core changes. + + 2.0.15 + ++++++ + * `role assignment list`: show default assignments for classic administrators + * `ad sp reset-credentials`: support to add credentials instead of overwriting + * `create-for-rbac`: emit out an actionable error if provisioning application failed for lack of permissions + + 2.0.14 + ++++++ + * minor fixes + + 2.0.13 (2017-10-09) + +++++++++++++++++++ + * minor fixes + 2.0.12 (2017-09-22) +++++++++++++++++++ * minor fixes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure_cli_role.egg-info/SOURCES.txt new/azure-cli-role-2.0.17/azure_cli_role.egg-info/SOURCES.txt --- old/azure-cli-role-2.0.12/azure_cli_role.egg-info/SOURCES.txt 2017-09-23 01:49:20.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure_cli_role.egg-info/SOURCES.txt 2018-01-12 18:25:50.000000000 +0100 @@ -8,6 +8,7 @@ azure/cli/command_modules/__init__.py azure/cli/command_modules/role/__init__.py azure/cli/command_modules/role/_client_factory.py +azure/cli/command_modules/role/_completers.py azure/cli/command_modules/role/_help.py azure/cli/command_modules/role/_params.py azure/cli/command_modules/role/_validators.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/azure_cli_role.egg-info/requires.txt new/azure-cli-role-2.0.17/azure_cli_role.egg-info/requires.txt --- old/azure-cli-role-2.0.12/azure_cli_role.egg-info/requires.txt 2017-09-23 01:49:20.000000000 +0200 +++ new/azure-cli-role-2.0.17/azure_cli_role.egg-info/requires.txt 2018-01-12 18:25:50.000000000 +0100 @@ -1,6 +1,5 @@ azure-cli-core azure-mgmt-authorization==0.30.0 azure-graphrbac==0.31.0 -azure-keyvault==0.3.6 +azure-keyvault==0.3.7 pytz -azure-cli-command-modules-nspkg>=2.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-role-2.0.12/setup.py new/azure-cli-role-2.0.17/setup.py --- old/azure-cli-role-2.0.12/setup.py 2017-09-23 01:49:19.000000000 +0200 +++ new/azure-cli-role-2.0.17/setup.py 2018-01-12 18:25:22.000000000 +0100 @@ -14,7 +14,7 @@ logger.warn("Wheel is not available, disabling bdist_wheel hook") cmdclass = {} -VERSION = "2.0.12" +VERSION = "2.0.17" CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -33,7 +33,7 @@ 'azure-cli-core', 'azure-mgmt-authorization==0.30.0', 'azure-graphrbac==0.31.0', - 'azure-keyvault==0.3.6', + 'azure-keyvault==0.3.7', 'pytz' ]
