Skip to content

Commit

Permalink
basic graph object read for erasier role assignment creation (#553)
Browse files Browse the repository at this point in the history
  • Loading branch information
yugangw-msft authored Jul 28, 2016
1 parent a2b19e8 commit 385df00
Show file tree
Hide file tree
Showing 9 changed files with 872 additions and 164 deletions.
4 changes: 2 additions & 2 deletions azure-cli.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
<InterpreterId>{54f4b6dc-0859-46dc-99bb-b275c9d0aca3}</InterpreterId>
<InterpreterVersion>3.5</InterpreterVersion>
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
<CommandLineArguments>
</CommandLineArguments>
<CommandLineArguments></CommandLineArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
Expand Down Expand Up @@ -155,6 +154,7 @@
<Compile Include="command_modules\azure-cli-resource\azure\cli\command_modules\resource\_factory.py">
<SubType>Code</SubType>
</Compile>
<Compile Include="command_modules\azure-cli-role\azure\cli\command_modules\role\custom.py" />
<Compile Include="command_modules\azure-cli-storage\azure\cli\command_modules\storage\_command_type.py" />
<Compile Include="command_modules\azure-cli-storage\azure\cli\command_modules\storage\_factory.py" />
<Compile Include="command_modules\azure-cli-vm\azure\cli\command_modules\vm\mgmt_acs\lib\acs_creation_client.py" />
Expand Down
22 changes: 12 additions & 10 deletions src/azure/cli/commands/client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,7 @@ def get_mgmt_service_client(client_type):
def get_subscription_service_client(client_type):
return _get_mgmt_service_client(client_type, False)

def _get_mgmt_service_client(client_type, subscription_bound=True):
logger.info('Getting management service client client_type=%s', client_type.__name__)
profile = Profile()
cred, subscription_id, _ = profile.get_login_credentials()
if subscription_bound:
client = client_type(cred, subscription_id)
else:
client = client_type(cred)

def configure_common_settings(client):
_debug.allow_debug_connection(client)

client.config.add_user_agent("AZURECLI/{}".format(cli.__version__))
Expand All @@ -43,8 +35,18 @@ def _get_mgmt_service_client(client_type, subscription_bound=True):
client.config.generate_client_request_id = \
'x-ms-client-request-id' not in APPLICATION.session['headers']

return (client, subscription_id)
def _get_mgmt_service_client(client_type, subscription_bound=True):
logger.info('Getting management service client client_type=%s', client_type.__name__)
profile = Profile()
cred, subscription_id, _ = profile.get_login_credentials()
if subscription_bound:
client = client_type(cred, subscription_id)
else:
client = client_type(cred)

configure_common_settings(client)

return (client, subscription_id)

def get_data_service_client(service_type, account_name, account_key, connection_string=None,
sas_token=None):
Expand Down
5 changes: 4 additions & 1 deletion src/azure/cli/utils/vcr_test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
TRACK_COMMANDS = os.environ.get('AZURE_CLI_TEST_TRACK_COMMANDS')
COMMAND_COVERAGE_FILENAME = 'command_coverage.txt'
MOCKED_SUBSCRIPTION_ID = '00000000-0000-0000-0000-000000000000'
MOCKED_TENANT_ID = '00000000-0000-0000-0000-000000000000'
# MOCK METHODS

def _mock_get_mgmt_service_client(client_type, subscription_bound=True):
Expand Down Expand Up @@ -62,7 +63,7 @@ def _mock_subscriptions(self): #pylint: disable=unused-argument
},
"state": "Enabled",
"name": "Example",
"tenantId": "123",
"tenantId": MOCKED_TENANT_ID,
"isDefault": True}]

def _mock_user_access_token(_, _1, _2, _3): #pylint: disable=unused-argument
Expand Down Expand Up @@ -198,6 +199,8 @@ def _before_record_request(request):
# scrub subscription from the uri
request.uri = re.sub('/subscriptions/([^/]+)/',
'/subscriptions/{}/'.format(MOCKED_SUBSCRIPTION_ID), request.uri)
request.uri = re.sub('/graph.windows.net/([^/]+)/',
'/graph.windows.net/{}/'.format(MOCKED_TENANT_ID), request.uri)
request.uri = re.sub('/sig=([^/]+)&', '/sig=0000&', request.uri)
request.uri = _scrub_deployment_name(request.uri)
# prevents URI mismatch between Python 2 and 3 if request URI has extra / chars
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def _build_output_content(sp_name, sp_object_id, secret, tenant):
logger.warning("Service principal has been configured with name: '%s', secret: '%s'",
sp_name, secret)
logger.warning('Useful commands to manage azure:')
logger.warning(' Assign a role: "az role assignment create --object-id %s --role Contributor"',
logger.warning(' Assign a role: "az role assignment create --assignee %s --role Contributor"',
sp_object_id)
logger.warning(' Log in: "az login --service-principal -u %s -p %s --tenant %s"',
sp_name, secret, tenant)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
#---------------------------------------------------------------------------------------------

from azure.mgmt.authorization import AuthorizationManagementClient

import azure.cli.commands.parameters #pylint: disable=unused-import

from azure.cli.commands.client_factory import get_mgmt_service_client

# FACTORIES
from azure.cli.commands import register_cli_argument

def _auth_client_factory(**_):
return get_mgmt_service_client(AuthorizationManagementClient)
register_cli_argument('ad app', 'application_object_id', options_list=('--object-id',))
register_cli_argument('ad app', 'app_id', help='application id')
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('role assignment', 'role_assignment_name',
options_list=('--role-assignment-name', '-n'))
register_cli_argument('role assignment', 'role', help='role name or id')
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')
Original file line number Diff line number Diff line change
@@ -1,30 +1,144 @@
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
#---------------------------------------------------------------------------------------------

import uuid
import re

from azure.cli._util import CLIError
from azure.cli.commands.client_factory import get_mgmt_service_client, configure_common_settings
from azure.mgmt.authorization import AuthorizationManagementClient
from azure.graphrbac import GraphRbacManagementClient
from azure.mgmt.authorization.models import RoleAssignmentProperties
from ._params import _auth_client_factory
from azure.graphrbac.models import UserCreateParameters, PasswordProfile

def _auth_client_factory(**_):
return get_mgmt_service_client(AuthorizationManagementClient)

def _graph_client_factory(**_):
from azure.cli._profile import Profile
profile = Profile()
cred, _, tenant_id = profile.get_login_credentials(True)
client = GraphRbacManagementClient(cred, tenant_id)
configure_common_settings(client)
return client

#TODO: expand the support to be in parity with node cli
def create_role_assignment(role, object_id, scope=None):
def create_role_assignment(role, assignee, resource_group_name=None, resource_id=None):
'''
:param assignee: represent a user, group, or service principal.
supported format: object id, user sign-in name, or service principal name
:param resource_id: resource id
'''
assignments_client = _auth_client_factory().role_assignments
definitions_client = _auth_client_factory().role_definitions
role_id = role
scope = scope or '/subscriptions/' + definitions_client.config.subscription_id
if not re.match(r'[0-9a-f]{32}\Z', role, re.I): #retrieve role id

if resource_id:
if resource_group_name:
err = 'Resource group "{}" is redundant because resource id is supplied'
raise CLIError(err.format(resource_group_name))
scope = resource_id
else:
scope = '/subscriptions/' + definitions_client.config.subscription_id
if resource_group_name:
scope = scope + '/resourceGroups/' + resource_group_name

role_id = None
try:
uuid.UUID(role)
role_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:
raise CLIError('Role {} doesn\'t exist.'.format(role))
raise CLIError("Role '{}' doesn't exist.".format(role))
elif len(role_defs) > 1:
raise CLIError('More than one roles match the given name {}'.format(role))
ids = [r.id for r in role_defs]
err = ("More than one role matches the given name '{}'. "
"Set 'role' to one of the unique ids from {}'")
raise CLIError(err.format(role, ids))
role_id = role_defs[0].id

object_id = _get_object_id(assignee)
properties = RoleAssignmentProperties(role_id, object_id)
assignment_name = uuid.uuid4()
return assignments_client.create(scope, assignment_name, properties)

def list_apps(client, app_id=None, display_name=None, identifier_uri=None, query_filter=None):
sub_filters = []
if query_filter:
sub_filters.append(query_filter)
if app_id:
sub_filters.append("appId eq '{}'".format(app_id))
if display_name:
sub_filters.append("startswith(displayName,'{}')".format(display_name))
if identifier_uri:
sub_filters.append("identifierUris/any(s:s eq '{}')".format(identifier_uri))

return client.list(filter=(' and '.join(sub_filters)))

def list_sps(client, spn=None, display_name=None, query_filter=None):
sub_filters = []
if query_filter:
sub_filters.append(query_filter)
if spn:
sub_filters.append("servicePrincipalNames/any(c:c eq '{}')".format(spn))
if display_name:
sub_filters.append("startswith(displayName,'{}')".format(display_name))

return client.list(filter=(' and '.join(sub_filters)))

def list_users(client, upn=None, display_name=None, query_filter=None):
sub_filters = []
if query_filter:
sub_filters.append(query_filter)
if upn:
sub_filters.append("userPrincipalName eq '{}'".format(upn))
if display_name:
sub_filters.append("startswith(displayName,'{}')".format(display_name))

return client.list(filter=(' and ').join(sub_filters))

def create_user(client, user_principal_name, display_name, password, mail_nickname=None, #pylint: disable=too-many-arguments
immutable_id=None, force_change_password_next_login=False):
'''
:param mail_nickname: mail alias. default to user principal name
'''
mail_nickname = mail_nickname or user_principal_name.split('@')[0]
param = UserCreateParameters(user_principal_name=user_principal_name, account_enabled=True,
display_name=display_name, mail_nickname=mail_nickname,
immutable_id=immutable_id,
password_profile=PasswordProfile(
password, force_change_password_next_login))
return client.create(param)

create_user.__doc__ = UserCreateParameters.__doc__

def list_groups(client, display_name=None, query_filter=None):
sub_filters = []
if query_filter:
sub_filters.append(query_filter)
if display_name:
sub_filters.append("startswith(displayName,'{}')".format(display_name))

return client.list(filter=(' and ').join(sub_filters))

def _get_object_id(assignee):
client = _graph_client_factory()
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
from azure.graphrbac.models import GetObjectsParameters
result = list(client.objects.get_objects_by_object_ids(
GetObjectsParameters(include_directory_object_references=True, object_ids=[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
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
from __future__ import print_function

from azure.mgmt.authorization.operations import RoleAssignmentsOperations, RoleDefinitionsOperations

from azure.graphrbac.operations import (ApplicationsOperations, ServicePrincipalsOperations,
UsersOperations, GroupsOperations)
from azure.cli.commands import cli_command

from ._params import _auth_client_factory
from .custom import create_role_assignment
from .custom import (create_role_assignment, list_sps, list_users, create_user, list_groups, list_apps,
_auth_client_factory, _graph_client_factory)

factory = lambda _: _auth_client_factory().role_definitions
cli_command('role list', RoleDefinitionsOperations.list, factory)
Expand All @@ -29,3 +30,27 @@
cli_command('role assignment list-for-resource-group', RoleAssignmentsOperations.list_for_resource_group, factory)
cli_command('role assignment list-for-scope', RoleAssignmentsOperations.list_for_scope, factory)
cli_command('role assignment create', create_role_assignment)

factory = lambda _: _graph_client_factory().applications
cli_command('ad app delete', ApplicationsOperations.delete, factory)
cli_command('ad app show', ApplicationsOperations.get, factory)
cli_command('ad app list', list_apps, factory)

factory = lambda _: _graph_client_factory().service_principals
cli_command('ad sp delete', ServicePrincipalsOperations.delete, factory)
cli_command('ad sp show', ServicePrincipalsOperations.get, factory)
#paging is broken at SDK https://github.com/Azure/azure-cli/issues/540
cli_command('ad sp list', list_sps, factory)

factory = lambda _: _graph_client_factory().users
cli_command('ad user delete', UsersOperations.delete, factory)
cli_command('ad user show', UsersOperations.get, factory)
#paging is broken at SDK https://github.com/Azure/azure-cli/issues/540
cli_command('ad user list', list_users, factory)
cli_command('ad user create', create_user, factory)

factory = lambda _: _graph_client_factory().groups
cli_command('ad group delete', GroupsOperations.delete, factory)
cli_command('ad group show', GroupsOperations.get, factory)
#paging is broken at SDK https://github.com/Azure/azure-cli/issues/540
cli_command('ad group list', list_groups, factory)
Loading

0 comments on commit 385df00

Please sign in to comment.