diff --git a/src/azure/cli/_azure_env.py b/src/azure/cli/_azure_env.py index 2aad9a54b91..9cc632da419 100644 --- a/src/azure/cli/_azure_env.py +++ b/src/azure/cli/_azure_env.py @@ -10,19 +10,23 @@ class ENDPOINT_URLS: #pylint: disable=too-few-public-methods,old-style-class,no-init MANAGEMENT = 'management' ACTIVE_DIRECTORY_AUTHORITY = 'active_directory_authority' + ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID = 'active_directory_graph_resource_id' _environments = { ENV_DEFAULT: { ENDPOINT_URLS.MANAGEMENT: 'https://management.core.windows.net/', - ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY : 'https://login.microsoftonline.com' + ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY : 'https://login.microsoftonline.com', + ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/' }, ENV_CHINA: { ENDPOINT_URLS.MANAGEMENT: 'https://management.core.chinacloudapi.cn/', - ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.chinacloudapi.cn' + ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.chinacloudapi.cn', + ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.chinacloudapi.cn/' }, ENV_US_GOVERNMENT: { ENDPOINT_URLS.MANAGEMENT: 'https://management.core.usgovcloudapi.net/', - ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.microsoftonline.com' + ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY: 'https://login.microsoftonline.com', + ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID: 'https://graph.windows.net/' } } @@ -36,8 +40,3 @@ def get_env(env_name=None): def get_authority_url(tenant=None, env_name=None): env = get_env(env_name) return env[ENDPOINT_URLS.ACTIVE_DIRECTORY_AUTHORITY] + '/' + (tenant or COMMON_TENANT) - -def get_management_endpoint_url(env_name=None): - env = get_env(env_name) - return env[ENDPOINT_URLS.MANAGEMENT] - diff --git a/src/azure/cli/_profile.py b/src/azure/cli/_profile.py index e55179cafd2..61f3cf82aac 100644 --- a/src/azure/cli/_profile.py +++ b/src/azure/cli/_profile.py @@ -9,8 +9,8 @@ from azure.mgmt.resource.subscriptions import SubscriptionClient from .main import ACCOUNT from ._util import CLIError -from ._azure_env import (get_authority_url, CLIENT_ID, get_management_endpoint_url, - ENV_DEFAULT, COMMON_TENANT) +from ._azure_env import (get_authority_url, get_env, ENDPOINT_URLS, + CLIENT_ID, ENV_DEFAULT, COMMON_TENANT) from .adal_authentication import AdalAuthentication import azure.cli._logging as _logging logger = _logging.get_az_logger(__name__) @@ -67,6 +67,9 @@ def __init__(self, storage=None, auth_ctx_factory=None): factory = auth_ctx_factory or _AUTH_CTX_FACTORY self._creds_cache = CredsCache(factory) self._subscription_finder = SubscriptionFinder(factory, self._creds_cache.adal_token_cache) + env = get_env() + self._management_resource_uri = env[ENDPOINT_URLS.MANAGEMENT] + self._graph_resource_uri = env[ENDPOINT_URLS.ACTIVE_DIRECTORY_GRAPH_RESOURCE_ID] def find_subscriptions_on_login(self, #pylint: disable=too-many-arguments interactive, @@ -77,17 +80,18 @@ def find_subscriptions_on_login(self, #pylint: disable=too-many-arguments self._creds_cache.remove_cached_creds(username) subscriptions = [] if interactive: - subscriptions = self._subscription_finder.find_through_interactive_flow() + subscriptions = self._subscription_finder.find_through_interactive_flow( + self._management_resource_uri) else: if is_service_principal: if not tenant: raise CLIError('Please supply tenant using "--tenant"') - subscriptions = self._subscription_finder.find_from_service_principal_id(username, - password, - tenant) + subscriptions = self._subscription_finder.find_from_service_principal_id( + username, password, tenant, self._management_resource_uri) else: - subscriptions = self._subscription_finder.find_from_user_account(username, password) + subscriptions = self._subscription_finder.find_from_user_account( + username, password, self._management_resource_uri) if not subscriptions: raise CLIError('No subscriptions found for this account.') @@ -192,7 +196,7 @@ def load_cached_subscriptions(self): def _cache_subscriptions_to_local_storage(self, subscriptions): self._storage[_SUBSCRIPTIONS] = subscriptions - def get_login_credentials(self): + def get_login_credentials(self, for_graph_client=False): subscriptions = self.load_cached_subscriptions() if not subscriptions: raise CLIError('Please run login to setup account.') @@ -204,16 +208,19 @@ def get_login_credentials(self): user_type = active_account[_USER_ENTITY][_USER_TYPE] username_or_sp_id = active_account[_USER_ENTITY][_USER_NAME] + resource = self._graph_resource_uri if for_graph_client else self._management_resource_uri if user_type == _USER: token_retriever = lambda: self._creds_cache.retrieve_token_for_user( - username_or_sp_id, active_account[_TENANT_ID]) + username_or_sp_id, active_account[_TENANT_ID], resource) auth_object = AdalAuthentication(token_retriever) else: token_retriever = lambda: self._creds_cache.retrieve_token_for_service_principal( - username_or_sp_id) + username_or_sp_id, resource) auth_object = AdalAuthentication(token_retriever) - return auth_object, str(active_account[_SUBSCRIPTION_ID]) + return (auth_object, + str(active_account[_SUBSCRIPTION_ID]), + str(active_account[_TENANT_ID])) class SubscriptionFinder(object): @@ -221,35 +228,34 @@ class SubscriptionFinder(object): def __init__(self, auth_context_factory, adal_token_cache, arm_client_factory=None): self._adal_token_cache = adal_token_cache self._auth_context_factory = auth_context_factory - self._resource = get_management_endpoint_url(ENV_DEFAULT) self.user_id = None # will figure out after log user in self._arm_client_factory = arm_client_factory or \ (lambda config: SubscriptionClient(config)) #pylint: disable=unnecessary-lambda - def find_from_user_account(self, username, password): + def find_from_user_account(self, username, password, resource): context = self._create_auth_context(COMMON_TENANT) token_entry = context.acquire_token_with_username_password( - self._resource, + resource, username, password, CLIENT_ID) self.user_id = token_entry[_TOKEN_ENTRY_USER_ID] - result = self._find_using_common_tenant(token_entry[_ACCESS_TOKEN]) + result = self._find_using_common_tenant(token_entry[_ACCESS_TOKEN], resource) return result - def find_through_interactive_flow(self): + def find_through_interactive_flow(self, resource): context = self._create_auth_context(COMMON_TENANT) - code = context.acquire_user_code(self._resource, CLIENT_ID) + code = context.acquire_user_code(resource, CLIENT_ID) logger.warning(code['message']) - token_entry = context.acquire_token_with_device_code(self._resource, code, CLIENT_ID) + token_entry = context.acquire_token_with_device_code(resource, code, CLIENT_ID) self.user_id = token_entry[_TOKEN_ENTRY_USER_ID] - result = self._find_using_common_tenant(token_entry[_ACCESS_TOKEN]) + result = self._find_using_common_tenant(token_entry[_ACCESS_TOKEN], resource) return result - def find_from_service_principal_id(self, client_id, secret, tenant): + def find_from_service_principal_id(self, client_id, secret, tenant, resource): context = self._create_auth_context(tenant, False) token_entry = context.acquire_token_with_client_credentials( - self._resource, + resource, client_id, secret) self.user_id = client_id @@ -261,7 +267,7 @@ def _create_auth_context(self, tenant, use_token_cache=True): authority = get_authority_url(tenant, ENV_DEFAULT) return self._auth_context_factory(authority, token_cache) - def _find_using_common_tenant(self, access_token): + def _find_using_common_tenant(self, access_token, resource): all_subscriptions = [] token_credential = BasicTokenAuthentication({'access_token': access_token}) client = self._arm_client_factory(token_credential) @@ -269,7 +275,7 @@ def _find_using_common_tenant(self, access_token): for t in tenants: tenant_id = t.tenant_id temp_context = self._create_auth_context(tenant_id) - temp_credentials = temp_context.acquire_token(self._resource, self.user_id, CLIENT_ID) + temp_credentials = temp_context.acquire_token(resource, self.user_id, CLIENT_ID) subscriptions = self._find_using_specific_tenant( tenant_id, temp_credentials[_ACCESS_TOKEN]) @@ -297,7 +303,6 @@ def __init__(self, auth_ctx_factory=None): self._auth_ctx_factory = auth_ctx_factory or _AUTH_CTX_FACTORY self.adal_token_cache = None self._load_creds() - self._resource = get_management_endpoint_url(ENV_DEFAULT) def persist_cached_creds(self): #be compatible with azure-xplat-cli, use 'ascii' so to save w/o a BOM @@ -314,10 +319,10 @@ def persist_cached_creds(self): cred_file.write(json.dumps(all_creds)) self.adal_token_cache.has_state_changed = False - def retrieve_token_for_user(self, username, tenant): + def retrieve_token_for_user(self, username, tenant, resource): authority = get_authority_url(tenant, ENV_DEFAULT) context = self._auth_ctx_factory(authority, cache=self.adal_token_cache) - token_entry = context.acquire_token(self._resource, username, CLIENT_ID) + token_entry = context.acquire_token(resource, username, CLIENT_ID) if not token_entry: raise CLIError('Could not retrieve token from local cache, please run \'login\'.') @@ -325,14 +330,14 @@ def retrieve_token_for_user(self, username, tenant): self.persist_cached_creds() return (token_entry[_TOKEN_ENTRY_TOKEN_TYPE], token_entry[_ACCESS_TOKEN]) - def retrieve_token_for_service_principal(self, sp_id): + def retrieve_token_for_service_principal(self, sp_id, resource): matched = [x for x in self._service_principal_creds if sp_id == x[_SERVICE_PRINCIPAL_ID]] if not matched: raise CLIError('Please run "account set" to select active account.') cred = matched[0] authority_url = get_authority_url(cred[_SERVICE_PRINCIPAL_TENANT], ENV_DEFAULT) context = self._auth_ctx_factory(authority_url, None) - token_entry = context.acquire_token_with_client_credentials(self._resource, + token_entry = context.acquire_token_with_client_credentials(resource, sp_id, cred[_ACCESS_TOKEN]) return (token_entry[_TOKEN_ENTRY_TOKEN_TYPE], token_entry[_ACCESS_TOKEN]) diff --git a/src/azure/cli/commands/client_factory.py b/src/azure/cli/commands/client_factory.py index 5990c113807..9a4566b615d 100644 --- a/src/azure/cli/commands/client_factory.py +++ b/src/azure/cli/commands/client_factory.py @@ -16,7 +16,7 @@ def get_subscription_service_client(client_type): 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() + cred, subscription_id, _ = profile.get_login_credentials() if subscription_bound: client = client_type(cred, subscription_id) else: diff --git a/src/azure/cli/tests/test_profile.py b/src/azure/cli/tests/test_profile.py index 9746a602438..d9d464122c8 100644 --- a/src/azure/cli/tests/test_profile.py +++ b/src/azure/cli/tests/test_profile.py @@ -197,7 +197,7 @@ def test_get_login_credentials(self, mock_get_token, mock_read_cred_file): ENV_DEFAULT) profile._set_subscriptions(consolidated) #action - cred, subscription_id = profile.get_login_credentials() + cred, subscription_id, _ = profile.get_login_credentials() #verify self.assertEqual(subscription_id, '1') @@ -207,8 +207,30 @@ def test_get_login_credentials(self, mock_get_token, mock_read_cred_file): self.assertEqual(token, self.raw_token1) self.assertEqual(some_token_type, token_type) self.assertEqual(mock_read_cred_file.call_count, 1) + mock_get_token.assert_called_once_with(mock.ANY, self.user1, self.tenant_id, + 'https://management.core.windows.net/') self.assertEqual(mock_get_token.call_count, 1) + @mock.patch('azure.cli._profile._read_file_content', autospec=True) + @mock.patch('azure.cli._profile.CredsCache.retrieve_token_for_user', autospec=True) + def test_get_login_credentials_for_graph_client(self, mock_get_token, mock_read_cred_file): + some_token_type = 'Bearer' + mock_read_cred_file.return_value = json.dumps([Test_Profile.token_entry1]) + mock_get_token.return_value = (some_token_type, Test_Profile.raw_token1) + #setup + storage_mock = {'subscriptions': None} + profile = Profile(storage_mock) + consolidated = Profile._normalize_properties(self.user1, [self.subscription1], + False, ENV_DEFAULT) + profile._set_subscriptions(consolidated) + #action + cred, _, tenant_id = profile.get_login_credentials(for_graph_client=True) + _, _ = cred._token_retriever() + #verify + mock_get_token.assert_called_once_with(mock.ANY, self.user1, self.tenant_id, + 'https://graph.windows.net/') + self.assertEqual(tenant_id, self.tenant_id) + @mock.patch('azure.cli._profile._read_file_content', autospec=True) @mock.patch('azure.cli._profile.CredsCache.persist_cached_creds', autospec=True) def test_logout(self, mock_persist_creds, mock_read_cred_file): @@ -264,16 +286,16 @@ def test_find_subscriptions_thru_username_password(self, mock_auth_context): finder = SubscriptionFinder(lambda _, _2: mock_auth_context, None, lambda _: mock_arm_client) - + mgmt_resource = 'https://management.core.windows.net/' #action - subs = finder.find_from_user_account(self.user1, 'bar') + subs = finder.find_from_user_account(self.user1, 'bar', mgmt_resource) #assert self.assertEqual([self.subscription1], subs) mock_auth_context.acquire_token_with_username_password.assert_called_once_with( - 'https://management.core.windows.net/', self.user1, 'bar', mock.ANY) + mgmt_resource, self.user1, 'bar', mock.ANY) mock_auth_context.acquire_token.assert_called_once_with( - 'https://management.core.windows.net/', self.user1, mock.ANY) + mgmt_resource, self.user1, mock.ANY) @mock.patch('adal.AuthenticationContext', autospec=True) def test_find_subscriptions_through_interactive_flow(self, mock_auth_context): @@ -286,18 +308,18 @@ def test_find_subscriptions_through_interactive_flow(self, mock_auth_context): finder = SubscriptionFinder(lambda _, _2: mock_auth_context, None, lambda _: mock_arm_client) - + mgmt_resource = 'https://management.core.windows.net/' #action - subs = finder.find_through_interactive_flow() + subs = finder.find_through_interactive_flow(mgmt_resource) #assert self.assertEqual([self.subscription1], subs) mock_auth_context.acquire_user_code.assert_called_once_with( - 'https://management.core.windows.net/', mock.ANY) + mgmt_resource, mock.ANY) mock_auth_context.acquire_token_with_device_code.assert_called_once_with( - 'https://management.core.windows.net/', test_nonsense_code, mock.ANY) + mgmt_resource, test_nonsense_code, mock.ANY) mock_auth_context.acquire_token.assert_called_once_with( - 'https://management.core.windows.net/', self.user1, mock.ANY) + mgmt_resource, self.user1, mock.ANY) @mock.patch('adal.AuthenticationContext', autospec=True) def test_find_subscriptions_from_service_principal_id(self, mock_auth_context): @@ -307,15 +329,17 @@ def test_find_subscriptions_from_service_principal_id(self, mock_auth_context): finder = SubscriptionFinder(lambda _, _2: mock_auth_context, None, lambda _: mock_arm_client) + mgmt_resource = 'https://management.core.windows.net/' #action - subs = finder.find_from_service_principal_id('my app', 'my secret', self.tenant_id) + subs = finder.find_from_service_principal_id('my app', 'my secret', + self.tenant_id, mgmt_resource) #assert self.assertEqual([self.subscription1], subs) mock_arm_client.tenants.list.assert_not_called() mock_auth_context.acquire_token.assert_not_called() mock_auth_context.acquire_token_with_client_credentials.assert_called_once_with( - 'https://management.core.windows.net/', 'my app', 'my secret') + mgmt_resource, 'my app', 'my secret') @mock.patch('azure.cli._profile._read_file_content', autospec=True) def test_credscache_load_tokens_and_sp_creds(self, mock_read_file): @@ -413,7 +437,9 @@ def get_auth_context(authority, **kwargs): # pylint: disable=unused-argument creds_cache = CredsCache(auth_ctx_factory=get_auth_context) #action - token_type, token = creds_cache.retrieve_token_for_user(self.user1, self.tenant_id) + mgmt_resource = 'https://management.core.windows.net/' + token_type, token = creds_cache.retrieve_token_for_user(self.user1, self.tenant_id, + mgmt_resource) mock_adal_auth_context.acquire_token.assert_called_once_with( 'https://management.core.windows.net/', self.user1, diff --git a/src/azure/cli/utils/vcr_test_base.py b/src/azure/cli/utils/vcr_test_base.py index f3c5330955f..2314096166d 100644 --- a/src/azure/cli/utils/vcr_test_base.py +++ b/src/azure/cli/utils/vcr_test_base.py @@ -30,7 +30,7 @@ def _mock_get_mgmt_service_client(client_type, subscription_bound=True): # version of _get_mgmt_service_client to use when recording or playing tests profile = Profile() - cred, subscription_id = profile.get_login_credentials() + cred, subscription_id, _ = profile.get_login_credentials() if subscription_bound: client = client_type(cred, subscription_id) else: @@ -60,7 +60,7 @@ def _mock_subscriptions(self): #pylint: disable=unused-argument "tenantId": "123", "isDefault": True}] -def _mock_user_access_token(_, _1, _2): #pylint: disable=unused-argument +def _mock_user_access_token(_, _1, _2, _3): #pylint: disable=unused-argument return ('Bearer', 'top-secret-token-for-you') def _mock_operation_delay(_): diff --git a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_param_folding.py b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_param_folding.py index d2e38197e4b..1b41997602a 100644 --- a/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_param_folding.py +++ b/src/command_modules/azure-cli-network/azure/cli/command_modules/network/_param_folding.py @@ -66,5 +66,5 @@ def handle_folding(namespace): def get_subscription_id(): from azure.cli.commands.client_factory import Profile profile = Profile() - _, subscription_id = profile.get_login_credentials() + _, subscription_id, _ = profile.get_login_credentials() return subscription_id diff --git a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py index 1f04e69ae8e..85961f2eb5e 100644 --- a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py +++ b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/_params.py @@ -37,6 +37,10 @@ def get_subscription_id_list(prefix, **kwargs):#pylint: disable=unused-argument help='Organization id or service principal' ) +sp_name_type = CliArgumentType( + options_list=('--name', '-n') +) + register_cli_argument('login', 'password', password_type) register_cli_argument('login', 'service_principal', service_principal_type) register_cli_argument('login', 'username', username_type) @@ -45,3 +49,6 @@ def get_subscription_id_list(prefix, **kwargs):#pylint: disable=unused-argument register_cli_argument('logout', 'username', username_type) register_cli_argument('account', 'subscription_name_or_id', subscription_name_or_id_type) + +register_cli_argument('account create-sp', 'name', sp_name_type) +register_cli_argument('account reset-sp-credentials', 'name', sp_name_type) diff --git a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py index f8cee61dd81..40cd1e90c26 100644 --- a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py +++ b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/custom.py @@ -1,8 +1,17 @@ -# pylint: disable=too-few-public-methods,too-many-arguments,no-self-use +# pylint: disable=too-few-public-methods,too-many-arguments,no-self-use #TODO: update adal-python to support it #from azure.cli._debug import should_disable_connection_verify +import datetime +import uuid +from dateutil.relativedelta import relativedelta + from adal.adal_error import AdalError +from azure.graphrbac.models import (ApplicationCreateParameters, + ApplicationUpdateParameters, + PasswordCredential) +from azure.graphrbac import GraphRbacManagementClient + from azure.cli._profile import Profile from azure.cli._util import CLIError import azure.cli._logging as _logging @@ -65,3 +74,85 @@ def list_location(): from azure.cli.commands.parameters import get_subscription_locations return get_subscription_locations() +def create_service_principal(name=None, secret=None, years=1): + '''create a service principal you can use with login command + + :param str name: an unique uri. If missing, the command will generate one. + :param str secret: the secret used to login. If missing, command will generate one. + :param str years: Years the secret will be valid. + ''' + start_date = datetime.datetime.now() + app_display_name = 'azure-cli-' + start_date.strftime('%Y-%m-%d-%H-%M-%S') + if name is None: + name = 'http://' + app_display_name + + key_id = str(uuid.uuid4()) + end_date = start_date + relativedelta(years=years) + secret = secret or str(uuid.uuid4()) + app_cred = PasswordCredential(start_date, end_date, key_id, secret) + app_create_param = ApplicationCreateParameters(False, app_display_name, + 'http://'+app_display_name, [name], + password_credentials=[app_cred]) + + profile = Profile() + cred, _, tenant = profile.get_login_credentials(for_graph_client=True) + + client = GraphRbacManagementClient(cred, tenant) + + #pylint: disable=no-member + aad_application = client.applications.create(app_create_param) + aad_sp = client.service_principals.create(aad_application.app_id, True) + + _build_output_content(name, aad_sp.object_id, secret, tenant) + +def reset_service_principal_credential(name, secret=None, years=1): + '''reset credential, on expiration or you forget it. + + :param str name: the uri representing the name of the service principal + :param str secret: the secret used to login. If missing, command will generate one. + :param str years: Years the secret will be valid. + ''' + profile = Profile() + cred, _, tenant = profile.get_login_credentials(for_graph_client=True) + client = GraphRbacManagementClient(cred, tenant) + + #pylint: disable=no-member + + #look for the existing application + query_exp = 'identifierUris/any(x:x eq \'{}\')'.format(name) + aad_apps = list(client.applications.list(filter=query_exp)) + if not aad_apps: + raise CLIError('can\'t find a graph application matching \'{}\''.format(name)) + #no need to check 2+ matches, as app id uri is unique + app = aad_apps[0] + + #look for the existing service principal + query_exp = 'servicePrincipalNames/any(x:x eq \'{}\')'.format(name) + aad_sps = list(client.service_principals.list(filter=query_exp)) + if not aad_sps: + raise CLIError('can\'t find an service principal matching \'{}\''.format(name)) + sp_object_id = aad_sps[0].object_id + + #build a new password credential and patch it + secret = secret or str(uuid.uuid4()) + start_date = datetime.datetime.now() + end_date = start_date + relativedelta(years=years) + key_id = str(uuid.uuid4()) + app_cred = PasswordCredential(start_date, end_date, key_id, secret) + app_create_param = ApplicationUpdateParameters(password_credentials=[app_cred]) + + client.applications.patch(app.object_id, app_create_param) + + _build_output_content(name, sp_object_id, secret, tenant) + +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"', + sp_object_id) + logger.warning(' Log in: "az login --service-principal -u %s -p %s --tenant %s"', + sp_name, secret, tenant) + logger.warning(' Reset credentials: "az account reset-sp-credentials --name %s"', + sp_name) + diff --git a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/generated.py b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/generated.py index d3b7e1c1833..5fca478e19c 100644 --- a/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/generated.py +++ b/src/command_modules/azure-cli-profile/azure/cli/command_modules/profile/generated.py @@ -2,8 +2,14 @@ from azure.cli.commands import cli_command -from .custom import (login, logout, list_subscriptions, set_active_subscription, account_clear, - list_location) +from .custom import (login, + logout, + list_location, + list_subscriptions, + set_active_subscription, + account_clear, + create_service_principal, + reset_service_principal_credential) cli_command('login', login) cli_command('logout', logout) @@ -13,3 +19,6 @@ cli_command('account clear', account_clear) cli_command('account list-location', list_location) +cli_command('account create-sp', create_service_principal) +cli_command('account reset-sp-credentials', reset_service_principal_credential) + diff --git a/src/command_modules/azure-cli-role/azure/cli/command_modules/role/custom.py b/src/command_modules/azure-cli-role/azure/cli/command_modules/role/custom.py new file mode 100644 index 00000000000..d03c135ab2f --- /dev/null +++ b/src/command_modules/azure-cli-role/azure/cli/command_modules/role/custom.py @@ -0,0 +1,25 @@ +import uuid +import re + +from azure.cli._util import CLIError +from azure.mgmt.authorization.models import RoleAssignmentProperties +from ._params import _auth_client_factory + +#TODO: expand the support to be in parity with node cli +def create_role_assignment(role, object_id, scope=None): + 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 + role_defs = list(definitions_client.list(scope, "roleName eq '{}'".format(role))) + if not role_defs: + 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)) + role_id = role_defs[0].id + + properties = RoleAssignmentProperties(role_id, object_id) + assignment_name = uuid.uuid4() + return assignments_client.create(scope, assignment_name, properties) + diff --git a/src/command_modules/azure-cli-role/azure/cli/command_modules/role/generated.py b/src/command_modules/azure-cli-role/azure/cli/command_modules/role/generated.py index 46e32568107..2c3524dce6e 100644 --- a/src/command_modules/azure-cli-role/azure/cli/command_modules/role/generated.py +++ b/src/command_modules/azure-cli-role/azure/cli/command_modules/role/generated.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long +# pylint: disable=line-too-long from __future__ import print_function from azure.mgmt.authorization.operations import RoleAssignmentsOperations, RoleDefinitionsOperations @@ -6,6 +6,7 @@ from azure.cli.commands import cli_command from ._params import _auth_client_factory +from .custom import create_role_assignment factory = lambda _: _auth_client_factory().role_definitions cli_command('role list', RoleDefinitionsOperations.list, factory) @@ -22,3 +23,4 @@ cli_command('role assignment list-for-resource', RoleAssignmentsOperations.list_for_resource, factory) 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) diff --git a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py index d18140c4c9c..db7ad784634 100644 --- a/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py +++ b/src/command_modules/azure-cli-vm/azure/cli/command_modules/vm/_actions.py @@ -257,5 +257,5 @@ def _handle_container_ssh_file(**kwargs): def _get_subscription_id(): from azure.cli.commands.client_factory import Profile profile = Profile() - _, subscription_id = profile.get_login_credentials() + _, subscription_id, _ = profile.get_login_credentials() return subscription_id diff --git a/src/command_modules/azure-cli-webapp/azure/cli/command_modules/webapp/_param_folding.py b/src/command_modules/azure-cli-webapp/azure/cli/command_modules/webapp/_param_folding.py index d2e38197e4b..1b41997602a 100644 --- a/src/command_modules/azure-cli-webapp/azure/cli/command_modules/webapp/_param_folding.py +++ b/src/command_modules/azure-cli-webapp/azure/cli/command_modules/webapp/_param_folding.py @@ -66,5 +66,5 @@ def handle_folding(namespace): def get_subscription_id(): from azure.cli.commands.client_factory import Profile profile = Profile() - _, subscription_id = profile.get_login_credentials() + _, subscription_id, _ = profile.get_login_credentials() return subscription_id