1# -------------------------------------------------------------------------------------------- 2# Copyright (c) Microsoft Corporation. All rights reserved. 3# Licensed under the MIT License. See License.txt in the project root for license information. 4# -------------------------------------------------------------------------------------------- 5 6from knack.util import CLIError 7from azure.cli.core.azclierror import InvalidArgumentValueError, ArgumentUsageError 8from azure.cli.core.util import is_guid 9from azure.graphrbac.models import GraphErrorException 10from msrestazure.azure_exceptions import CloudError 11from .._client_factory import cf_synapse_role_assignments, cf_synapse_role_definitions, cf_graph_client_factory 12from ..constant import ITEM_NAME_MAPPING 13 14 15# List Synapse Role Assignment 16def list_role_assignments(cmd, workspace_name, role=None, assignee=None, assignee_object_id=None, 17 scope=None, item=None, item_type=None): 18 if bool(assignee) and bool(assignee_object_id): 19 raise ArgumentUsageError('usage error: --assignee STRING | --assignee-object-id GUID') 20 21 if bool(item) != bool(item_type): 22 raise ArgumentUsageError('usage error: --item-type STRING --item STRING') 23 24 return _list_role_assignments(cmd, workspace_name, role, assignee or assignee_object_id, 25 scope, resolve_assignee=(not assignee_object_id), item=item, item_type=item_type) 26 27 28def _list_role_assignments(cmd, workspace_name, role=None, assignee=None, scope=None, 29 resolve_assignee=True, item=None, item_type=None): 30 """Prepare scope, role ID and resolve object ID from Graph API.""" 31 if any([scope, item, item_type]): 32 scope = _build_role_scope(workspace_name, scope, item, item_type) 33 role_id = _resolve_role_id(cmd, role, workspace_name) 34 object_id = _resolve_object_id(cmd, assignee, fallback_to_object_id=True) if resolve_assignee else assignee 35 client = cf_synapse_role_assignments(cmd.cli_ctx, workspace_name) 36 role_assignments = client.list_role_assignments(role_id, object_id, scope).value 37 return role_assignments 38 39 40# Show Synapse Role Assignment By Id 41def get_role_assignment_by_id(cmd, workspace_name, role_assignment_id): 42 client = cf_synapse_role_assignments(cmd.cli_ctx, workspace_name) 43 return client.get_role_assignment_by_id(role_assignment_id) 44 45 46# Delete Synapse Role Assignment 47def delete_role_assignment(cmd, workspace_name, ids=None, assignee=None, assignee_object_id=None, role=None, 48 scope=None, item=None, item_type=None): 49 client = cf_synapse_role_assignments(cmd.cli_ctx, workspace_name) 50 if not any([ids, assignee, assignee_object_id, role, scope, item, item_type]): 51 raise ArgumentUsageError('usage error: No argument are provided. --assignee STRING | --ids GUID') 52 53 if ids: 54 if any([assignee, assignee_object_id, role, scope, item, item_type]): 55 raise ArgumentUsageError('You should not provide --role or --assignee or --assignee_object_id ' 56 'or --scope or --principal-type when --ids is provided.') 57 role_assignments = list_role_assignments(cmd, workspace_name, None, None, None, None, None, None) 58 assignment_id_list = [x.id for x in role_assignments] 59 # check role assignment id 60 for assignment_id in ids: 61 if assignment_id not in assignment_id_list: 62 raise ArgumentUsageError("role assignment id:'{}' doesn't exist.".format(assignment_id)) 63 # delete when all ids check pass 64 for assignment_id in ids: 65 client.delete_role_assignment_by_id(assignment_id) 66 return 67 68 role_assignments = list_role_assignments(cmd, workspace_name, role, assignee, assignee_object_id, 69 scope, item, item_type) 70 if any([scope, item, item_type]): 71 scope = _build_role_scope(workspace_name, scope, item, item_type) 72 role_assignments = [x for x in role_assignments if x.scope == scope] 73 74 if role_assignments: 75 for assignment in role_assignments: 76 client.delete_role_assignment_by_id(assignment.id) 77 else: 78 raise CLIError('No matched assignments were found to delete, please provide correct --role or --assignee.' 79 'Use `az synapse role assignment list` to get role assignments.') 80 81 82def create_role_assignment(cmd, workspace_name, role, assignee=None, assignee_object_id=None, 83 scope=None, assignee_principal_type=None, item_type=None, item=None, assignment_id=None): 84 """Check parameters are provided correctly, then call _create_role_assignment.""" 85 if assignment_id and not is_guid(assignment_id): 86 raise InvalidArgumentValueError('usage error: --id GUID') 87 88 if bool(assignee) == bool(assignee_object_id): 89 raise ArgumentUsageError('usage error: --assignee STRING | --assignee-object-id GUID') 90 91 if assignee_principal_type and not assignee_object_id: 92 raise ArgumentUsageError('usage error: --assignee-object-id GUID [--assignee-principal-type]') 93 94 if bool(item) != bool(item_type): 95 raise ArgumentUsageError('usage error: --item-type STRING --item STRING') 96 97 try: 98 return _create_role_assignment(cmd, workspace_name, role, assignee or assignee_object_id, scope, item, 99 item_type, resolve_assignee=(not assignee_object_id), 100 assignee_principal_type=assignee_principal_type, assignment_id=assignment_id) 101 except Exception as ex: # pylint: disable=broad-except 102 if _error_caused_by_role_assignment_exists(ex): # for idempotent 103 return list_role_assignments(cmd, workspace_name, role=role, 104 assignee=assignee, assignee_object_id=assignee_object_id, 105 scope=scope, item=item, item_type=item_type) 106 raise 107 108 109def _resolve_object_id(cmd, assignee, fallback_to_object_id=False): 110 if assignee is None: 111 return None 112 client = cf_graph_client_factory(cmd.cli_ctx) 113 result = None 114 try: 115 result = list(client.users.list(filter="userPrincipalName eq '{0}' or mail eq '{0}' or displayName eq '{0}'" 116 .format(assignee))) 117 if not result: 118 result = list(client.service_principals.list(filter="displayName eq '{}'".format(assignee))) 119 if not result: 120 result = list(client.groups.list(filter="mail eq '{}'".format(assignee))) 121 if not result and is_guid(assignee): # assume an object id, let us verify it 122 result = _get_object_stubs(client, [assignee]) 123 124 # 2+ matches should never happen, so we only check 'no match' here 125 if not result: 126 raise CLIError("Cannot find user or group or service principal in graph database for '{assignee}'. " 127 "If the assignee is a principal id, make sure the corresponding principal is created " 128 "with 'az ad sp create --id {assignee}'.".format(assignee=assignee)) 129 130 if len(result) > 1: 131 raise CLIError("Find more than one user or group or service principal in graph database for '{assignee}'. " 132 "Please using --assignee-object-id GUID to specify assignee accurately" 133 .format(assignee=assignee)) 134 135 return result[0].object_id 136 except (CloudError, GraphErrorException): 137 if fallback_to_object_id and is_guid(assignee): 138 return assignee 139 raise 140 141 142def _get_object_stubs(graph_client, assignees): 143 from azure.graphrbac.models import GetObjectsParameters 144 result = [] 145 assignees = list(assignees) # callers could pass in a set 146 for i in range(0, len(assignees), 1000): 147 params = GetObjectsParameters(include_directory_object_references=True, object_ids=assignees[i:i + 1000]) 148 result += list(graph_client.objects.get_objects_by_object_ids(params)) 149 return result 150 151 152def _error_caused_by_role_assignment_exists(ex): 153 return getattr(ex, 'status_code', None) == 409 and 'role assignment already exists' in ex.message 154 155 156def _create_role_assignment(cmd, workspace_name, role, assignee, scope=None, item=None, item_type=None, 157 resolve_assignee=True, assignee_principal_type=None, assignment_id=None): 158 """Prepare scope, role ID and resolve object ID from Graph API.""" 159 scope = _build_role_scope(workspace_name, scope, item, item_type) 160 role_id = _resolve_role_id(cmd, role, workspace_name) 161 object_id = _resolve_object_id(cmd, assignee, fallback_to_object_id=True) if resolve_assignee else assignee 162 163 assignment_client = cf_synapse_role_assignments(cmd.cli_ctx, workspace_name) 164 return assignment_client.create_role_assignment(assignment_id if assignment_id is not None else _gen_guid(), 165 role_id, object_id, scope, assignee_principal_type) 166 167 168def _build_role_scope(workspace_name, scope, item, item_type): 169 if scope: 170 return scope 171 172 if item and item_type: 173 # workspaces/{workspaceName}/bigDataPools/{bigDataPoolName} 174 scope = "workspaces/" + workspace_name + "/" + item_type + "/" + item 175 else: 176 scope = "workspaces/" + workspace_name 177 178 return scope 179 180 181def _resolve_role_id(cmd, role, workspace_name): 182 role_id = None 183 if not role: 184 return role_id 185 if is_guid(role): 186 role_id = role 187 else: 188 role_definition_client = cf_synapse_role_definitions(cmd.cli_ctx, workspace_name) 189 role_definition = role_definition_client.list_role_definitions() 190 role_dict = {x.name.lower(): x.id for x in role_definition if x.name} 191 if role.lower() not in role_dict: 192 raise CLIError("Role '{}' doesn't exist.".format(role)) 193 role_id = role_dict[role.lower()] 194 return role_id 195 196 197def _gen_guid(): 198 import uuid 199 return uuid.uuid4() 200 201 202# List Synapse Role Definitions Scope 203def list_scopes(cmd, workspace_name): 204 client = cf_synapse_role_definitions(cmd.cli_ctx, workspace_name) 205 return client.list_scopes() 206 207 208# List Synapse Role Definitions 209def list_role_definitions(cmd, workspace_name, is_built_in=None): 210 client = cf_synapse_role_definitions(cmd.cli_ctx, workspace_name) 211 role_definitions = client.list_role_definitions(is_built_in) 212 return role_definitions 213 214 215def _build_role_scope_format(scope, item_type): 216 if scope: 217 return scope 218 219 if item_type: 220 scope = "workspaces/{workspaceName}/" + item_type + "/" + ITEM_NAME_MAPPING[item_type] 221 else: 222 scope = "workspaces/{workspaceName}" 223 224 return scope 225 226 227# Get Synapse Role Definition 228def get_role_definition(cmd, workspace_name, role): 229 role_id = _resolve_role_id(cmd, role, workspace_name) 230 client = cf_synapse_role_definitions(cmd.cli_ctx, workspace_name) 231 return client.get_role_definition_by_id(role_id) 232