Adds a method for creating an Active Directory user.
This method is added to the Azure cloud interface. We need to set the AAD user's alternate email, which is a subsequent PATCH call to the API. These two calls are handled with a single interface method and payload because ATAT would never create a user without an associated email. This commit also: - Expands internal method for getting principal tokens so that it can be scoped to different resources. - Retains the tenant domain name in the portfolios.csp_data column because ATAT needs that information for provisioning users via API.
This commit is contained in:
parent
cc28f53999
commit
b1c6dd5ad0
@ -6,7 +6,7 @@ from uuid import uuid4
|
||||
from atst.utils import sha256_hex
|
||||
|
||||
from .cloud_provider_interface import CloudProviderInterface
|
||||
from .exceptions import AuthenticationException
|
||||
from .exceptions import AuthenticationException, UserProvisioningException
|
||||
from .models import (
|
||||
SubscriptionCreationCSPPayload,
|
||||
SubscriptionCreationCSPResult,
|
||||
@ -48,6 +48,8 @@ from .models import (
|
||||
TenantPrincipalCSPResult,
|
||||
TenantPrincipalOwnershipCSPPayload,
|
||||
TenantPrincipalOwnershipCSPResult,
|
||||
UserCSPPayload,
|
||||
UserCSPResult,
|
||||
)
|
||||
from .policy import AzurePolicyManager
|
||||
|
||||
@ -191,6 +193,7 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
|
||||
def create_application(self, payload: ApplicationCSPPayload):
|
||||
creds = self._source_creds(payload.tenant_id)
|
||||
# TODO: these should be tenant_sp_client_id, etc
|
||||
credentials = self._get_credential_obj(
|
||||
{
|
||||
"client_id": creds.root_sp_client_id,
|
||||
@ -310,7 +313,9 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
tenant_admin_password=payload.password,
|
||||
),
|
||||
)
|
||||
return self._ok(TenantCSPResult(**result_dict))
|
||||
return self._ok(
|
||||
TenantCSPResult(domain_name=payload.domain_name, **result_dict)
|
||||
)
|
||||
else:
|
||||
return self._error(result.json())
|
||||
|
||||
@ -850,6 +855,80 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
|
||||
return service_principal
|
||||
|
||||
def create_user(self, payload: UserCSPPayload) -> UserCSPResult:
|
||||
"""Create a user in an Azure Active Directory instance.
|
||||
Unlike most of the methods on this interface, this requires
|
||||
two API calls: one POST to create the user and one PATCH to
|
||||
set the alternate email address. The email address cannot
|
||||
be set on the first API call. The email address is
|
||||
necessary so that users can do Self-Service Password
|
||||
Recovery.
|
||||
|
||||
Arguments:
|
||||
payload {UserCSPPayload} -- a payload object with the
|
||||
data necessary for both calls
|
||||
|
||||
Returns:
|
||||
UserCSPResult -- a result object containing the AAD ID.
|
||||
"""
|
||||
graph_token = self._get_tenant_principal_token(
|
||||
payload.tenant_id, resource=self.graph_resource
|
||||
)
|
||||
if graph_token is None:
|
||||
raise AuthenticationException(
|
||||
"Could not resolve graph token for tenant admin"
|
||||
)
|
||||
|
||||
result = self._create_active_directory_user(graph_token, payload)
|
||||
self._update_active_directory_user_email(graph_token, result.id, payload)
|
||||
|
||||
return result
|
||||
|
||||
def _create_active_directory_user(self, graph_token, payload: UserCSPPayload):
|
||||
request_body = {
|
||||
"accountEnabled": True,
|
||||
"displayName": payload.display_name,
|
||||
"mailNickname": payload.mail_nickname,
|
||||
"userPrincipalName": payload.user_principal_name,
|
||||
"passwordProfile": {
|
||||
"forceChangePasswordNextSignIn": True,
|
||||
"password": payload.password,
|
||||
},
|
||||
}
|
||||
|
||||
auth_header = {
|
||||
"Authorization": f"Bearer {graph_token}",
|
||||
}
|
||||
|
||||
url = f"{self.graph_resource}v1.0/users"
|
||||
|
||||
response = self.sdk.requests.post(url, headers=auth_header, json=request_body)
|
||||
|
||||
if response.ok:
|
||||
return UserCSPResult(**response.json())
|
||||
else:
|
||||
raise UserProvisioningException(f"Failed to create user: {response.json()}")
|
||||
|
||||
def _update_active_directory_user_email(
|
||||
self, graph_token, user_id, payload: UserCSPPayload
|
||||
):
|
||||
request_body = {"otherMails": [payload.email]}
|
||||
|
||||
auth_header = {
|
||||
"Authorization": f"Bearer {graph_token}",
|
||||
}
|
||||
|
||||
url = f"{self.graph_resource}v1.0/users/{user_id}"
|
||||
|
||||
response = self.sdk.requests.patch(url, headers=auth_header, json=request_body)
|
||||
|
||||
if response.ok:
|
||||
return True
|
||||
else:
|
||||
raise UserProvisioningException(
|
||||
f"Failed update user email: {response.json()}"
|
||||
)
|
||||
|
||||
def _extract_subscription_id(self, subscription_url):
|
||||
sub_id_match = SUBSCRIPTION_ID_REGEX.match(subscription_url)
|
||||
|
||||
@ -871,14 +950,15 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
creds.root_tenant_id, creds.root_sp_client_id, creds.root_sp_key
|
||||
)
|
||||
|
||||
def _get_sp_token(self, tenant_id, client_id, secret_key):
|
||||
def _get_sp_token(self, tenant_id, client_id, secret_key, resource=None):
|
||||
context = self.sdk.adal.AuthenticationContext(
|
||||
f"{self.sdk.cloud.endpoints.active_directory}/{tenant_id}"
|
||||
)
|
||||
|
||||
resource = resource or self.sdk.cloud.endpoints.resource_manager
|
||||
# TODO: handle failure states here
|
||||
token_response = context.acquire_token_with_client_credentials(
|
||||
self.sdk.cloud.endpoints.resource_manager, client_id, secret_key
|
||||
resource, client_id, secret_key
|
||||
)
|
||||
|
||||
return token_response.get("accessToken", None)
|
||||
@ -939,10 +1019,13 @@ class AzureCloudProvider(CloudProviderInterface):
|
||||
"tenant_id": self.tenant_id,
|
||||
}
|
||||
|
||||
def _get_tenant_principal_token(self, tenant_id):
|
||||
def _get_tenant_principal_token(self, tenant_id, resource=None):
|
||||
creds = self._source_creds(tenant_id)
|
||||
return self._get_sp_token(
|
||||
creds.tenant_id, creds.tenant_sp_client_id, creds.tenant_sp_key
|
||||
creds.tenant_id,
|
||||
creds.tenant_sp_client_id,
|
||||
creds.tenant_sp_key,
|
||||
resource=resource,
|
||||
)
|
||||
|
||||
def _get_elevated_management_token(self, tenant_id):
|
||||
|
@ -88,17 +88,6 @@ class UserProvisioningException(GeneralCSPException):
|
||||
"""Failed to provision a user
|
||||
"""
|
||||
|
||||
def __init__(self, env_identifier, user_identifier, reason):
|
||||
self.env_identifier = env_identifier
|
||||
self.user_identifier = user_identifier
|
||||
self.reason = reason
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return "Failed to create user {} for environment {}: {}".format(
|
||||
self.user_identifier, self.env_identifier, self.reason
|
||||
)
|
||||
|
||||
|
||||
class UserRemovalException(GeneralCSPException):
|
||||
"""Failed to remove a user
|
||||
|
@ -175,6 +175,7 @@ class MockCloudProvider(CloudProviderInterface):
|
||||
"tenant_id": "",
|
||||
"user_id": "",
|
||||
"user_object_id": "",
|
||||
"domain_name": "",
|
||||
"tenant_admin_username": "test",
|
||||
"tenant_admin_password": "test",
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ class TenantCSPResult(AliasModel):
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
user_object_id: str
|
||||
domain_name: str
|
||||
|
||||
tenant_admin_username: Optional[str]
|
||||
tenant_admin_password: Optional[str]
|
||||
@ -474,3 +475,25 @@ class ProductPurchaseVerificationCSPPayload(BaseCSPPayload):
|
||||
|
||||
class ProductPurchaseVerificationCSPResult(AliasModel):
|
||||
premium_purchase_date: str
|
||||
|
||||
|
||||
class UserCSPPayload(BaseCSPPayload):
|
||||
# userPrincipalName must be username + tenant
|
||||
# display name should be full name
|
||||
# mail nickname should be... email address?
|
||||
display_name: str
|
||||
tenant_host_name: str
|
||||
email: str
|
||||
password: str
|
||||
|
||||
@property
|
||||
def user_principal_name(self):
|
||||
return f"{self.mail_nickname}@{self.tenant_host_name}"
|
||||
|
||||
@property
|
||||
def mail_nickname(self):
|
||||
return self.display_name.replace(" ", ".").lower()
|
||||
|
||||
|
||||
class UserCSPResult(AliasModel):
|
||||
id: str
|
||||
|
Loading…
x
Reference in New Issue
Block a user