step in state machine to reset tenant admin password to random value

This commit is contained in:
Philip Kalinsky 2020-03-05 12:57:56 -05:00
parent f2dbd4fbc7
commit 16cf5dfe00
7 changed files with 426 additions and 0 deletions

View File

@ -0,0 +1,297 @@
"""tenant admin password update
Revision ID: 18b29ab247fb
Revises: 9f2813487e00
Create Date: 2020-03-05 12:37:42.056513
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '18b29ab247fb' # pragma: allowlist secret
down_revision = '9f2813487e00' # pragma: allowlist secret
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"portfolio_state_machines",
"state",
type_=sa.Enum(
"UNSTARTED",
"STARTING",
"STARTED",
"COMPLETED",
"FAILED",
"TENANT_CREATED",
"TENANT_IN_PROGRESS",
"TENANT_FAILED",
"BILLING_PROFILE_CREATION_CREATED",
"BILLING_PROFILE_CREATION_IN_PROGRESS",
"BILLING_PROFILE_CREATION_FAILED",
"BILLING_PROFILE_VERIFICATION_CREATED",
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
"BILLING_PROFILE_VERIFICATION_FAILED",
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
"TASK_ORDER_BILLING_CREATION_CREATED",
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
"TASK_ORDER_BILLING_CREATION_FAILED",
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
"BILLING_INSTRUCTION_CREATED",
"BILLING_INSTRUCTION_IN_PROGRESS",
"BILLING_INSTRUCTION_FAILED",
"PRODUCT_PURCHASE_CREATED",
"PRODUCT_PURCHASE_IN_PROGRESS",
"PRODUCT_PURCHASE_FAILED",
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
"TENANT_PRINCIPAL_APP_CREATED",
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
"TENANT_PRINCIPAL_APP_FAILED",
"TENANT_PRINCIPAL_CREATED",
"TENANT_PRINCIPAL_IN_PROGRESS",
"TENANT_PRINCIPAL_FAILED",
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
"ADMIN_ROLE_DEFINITION_CREATED",
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
"ADMIN_ROLE_DEFINITION_FAILED",
"PRINCIPAL_ADMIN_ROLE_CREATED",
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
"PRINCIPAL_ADMIN_ROLE_FAILED",
"INITIAL_MGMT_GROUP_CREATED",
"INITIAL_MGMT_GROUP_IN_PROGRESS",
"INITIAL_MGMT_GROUP_FAILED",
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
"TENANT_ADMIN_OWNERSHIP_CREATED",
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
"TENANT_ADMIN_OWNERSHIP_FAILED",
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
"BILLING_OWNER_CREATED",
"BILLING_OWNER_IN_PROGRESS",
"BILLING_OWNER_FAILED",
name="fsmstates",
native_enum=False,
),
existing_type=sa.Enum(
"UNSTARTED",
"STARTING",
"STARTED",
"COMPLETED",
"FAILED",
"TENANT_CREATED",
"TENANT_IN_PROGRESS",
"TENANT_FAILED",
"BILLING_PROFILE_CREATION_CREATED",
"BILLING_PROFILE_CREATION_IN_PROGRESS",
"BILLING_PROFILE_CREATION_FAILED",
"BILLING_PROFILE_VERIFICATION_CREATED",
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
"BILLING_PROFILE_VERIFICATION_FAILED",
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
"TASK_ORDER_BILLING_CREATION_CREATED",
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
"TASK_ORDER_BILLING_CREATION_FAILED",
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
"BILLING_INSTRUCTION_CREATED",
"BILLING_INSTRUCTION_IN_PROGRESS",
"BILLING_INSTRUCTION_FAILED",
"PRODUCT_PURCHASE_CREATED",
"PRODUCT_PURCHASE_IN_PROGRESS",
"PRODUCT_PURCHASE_FAILED",
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
"TENANT_PRINCIPAL_APP_CREATED",
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
"TENANT_PRINCIPAL_APP_FAILED",
"TENANT_PRINCIPAL_CREATED",
"TENANT_PRINCIPAL_IN_PROGRESS",
"TENANT_PRINCIPAL_FAILED",
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
"ADMIN_ROLE_DEFINITION_CREATED",
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
"ADMIN_ROLE_DEFINITION_FAILED",
"PRINCIPAL_ADMIN_ROLE_CREATED",
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
"PRINCIPAL_ADMIN_ROLE_FAILED",
"INITIAL_MGMT_GROUP_CREATED",
"INITIAL_MGMT_GROUP_IN_PROGRESS",
"INITIAL_MGMT_GROUP_FAILED",
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
"TENANT_ADMIN_OWNERSHIP_CREATED",
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
"TENANT_ADMIN_OWNERSHIP_FAILED",
"TENANT_ADMIN_CREDENTIAL_RESET_CREATED",
"TENANT_ADMIN_CREDENTIAL_RESET_IN_PROGRESS",
"TENANT_ADMIN_CREDENTIAL_RESET_FAILED",
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
name="fsmstates",
native_enum=False,
),
existing_nullable=False,
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column(
"portfolio_state_machines",
"state",
type_=sa.Enum(
"UNSTARTED",
"STARTING",
"STARTED",
"COMPLETED",
"FAILED",
"TENANT_CREATED",
"TENANT_IN_PROGRESS",
"TENANT_FAILED",
"BILLING_PROFILE_CREATION_CREATED",
"BILLING_PROFILE_CREATION_IN_PROGRESS",
"BILLING_PROFILE_CREATION_FAILED",
"BILLING_PROFILE_VERIFICATION_CREATED",
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
"BILLING_PROFILE_VERIFICATION_FAILED",
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
"TASK_ORDER_BILLING_CREATION_CREATED",
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
"TASK_ORDER_BILLING_CREATION_FAILED",
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
"BILLING_INSTRUCTION_CREATED",
"BILLING_INSTRUCTION_IN_PROGRESS",
"BILLING_INSTRUCTION_FAILED",
"PRODUCT_PURCHASE_CREATED",
"PRODUCT_PURCHASE_IN_PROGRESS",
"PRODUCT_PURCHASE_FAILED",
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
"TENANT_PRINCIPAL_APP_CREATED",
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
"TENANT_PRINCIPAL_APP_FAILED",
"TENANT_PRINCIPAL_CREATED",
"TENANT_PRINCIPAL_IN_PROGRESS",
"TENANT_PRINCIPAL_FAILED",
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
"ADMIN_ROLE_DEFINITION_CREATED",
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
"ADMIN_ROLE_DEFINITION_FAILED",
"PRINCIPAL_ADMIN_ROLE_CREATED",
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
"PRINCIPAL_ADMIN_ROLE_FAILED",
"INITIAL_MGMT_GROUP_CREATED",
"INITIAL_MGMT_GROUP_IN_PROGRESS",
"INITIAL_MGMT_GROUP_FAILED",
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
"TENANT_ADMIN_OWNERSHIP_CREATED",
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
"TENANT_ADMIN_OWNERSHIP_FAILED",
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
name="fsmstates",
native_enum=False,
),
existing_type=sa.Enum(
"UNSTARTED",
"STARTING",
"STARTED",
"COMPLETED",
"FAILED",
"TENANT_CREATED",
"TENANT_IN_PROGRESS",
"TENANT_FAILED",
"BILLING_PROFILE_CREATION_CREATED",
"BILLING_PROFILE_CREATION_IN_PROGRESS",
"BILLING_PROFILE_CREATION_FAILED",
"BILLING_PROFILE_VERIFICATION_CREATED",
"BILLING_PROFILE_VERIFICATION_IN_PROGRESS",
"BILLING_PROFILE_VERIFICATION_FAILED",
"BILLING_PROFILE_TENANT_ACCESS_CREATED",
"BILLING_PROFILE_TENANT_ACCESS_IN_PROGRESS",
"BILLING_PROFILE_TENANT_ACCESS_FAILED",
"TASK_ORDER_BILLING_CREATION_CREATED",
"TASK_ORDER_BILLING_CREATION_IN_PROGRESS",
"TASK_ORDER_BILLING_CREATION_FAILED",
"TASK_ORDER_BILLING_VERIFICATION_CREATED",
"TASK_ORDER_BILLING_VERIFICATION_IN_PROGRESS",
"TASK_ORDER_BILLING_VERIFICATION_FAILED",
"BILLING_INSTRUCTION_CREATED",
"BILLING_INSTRUCTION_IN_PROGRESS",
"BILLING_INSTRUCTION_FAILED",
"PRODUCT_PURCHASE_CREATED",
"PRODUCT_PURCHASE_IN_PROGRESS",
"PRODUCT_PURCHASE_FAILED",
"PRODUCT_PURCHASE_VERIFICATION_CREATED",
"PRODUCT_PURCHASE_VERIFICATION_IN_PROGRESS",
"PRODUCT_PURCHASE_VERIFICATION_FAILED",
"TENANT_PRINCIPAL_APP_CREATED",
"TENANT_PRINCIPAL_APP_IN_PROGRESS",
"TENANT_PRINCIPAL_APP_FAILED",
"TENANT_PRINCIPAL_CREATED",
"TENANT_PRINCIPAL_IN_PROGRESS",
"TENANT_PRINCIPAL_FAILED",
"TENANT_PRINCIPAL_CREDENTIAL_CREATED",
"TENANT_PRINCIPAL_CREDENTIAL_IN_PROGRESS",
"TENANT_PRINCIPAL_CREDENTIAL_FAILED",
"ADMIN_ROLE_DEFINITION_CREATED",
"ADMIN_ROLE_DEFINITION_IN_PROGRESS",
"ADMIN_ROLE_DEFINITION_FAILED",
"PRINCIPAL_ADMIN_ROLE_CREATED",
"PRINCIPAL_ADMIN_ROLE_IN_PROGRESS",
"PRINCIPAL_ADMIN_ROLE_FAILED",
"INITIAL_MGMT_GROUP_CREATED",
"INITIAL_MGMT_GROUP_IN_PROGRESS",
"INITIAL_MGMT_GROUP_FAILED",
"INITIAL_MGMT_GROUP_VERIFICATION_CREATED",
"INITIAL_MGMT_GROUP_VERIFICATION_IN_PROGRESS",
"INITIAL_MGMT_GROUP_VERIFICATION_FAILED",
"TENANT_ADMIN_OWNERSHIP_CREATED",
"TENANT_ADMIN_OWNERSHIP_IN_PROGRESS",
"TENANT_ADMIN_OWNERSHIP_FAILED",
"TENANT_PRINCIPAL_OWNERSHIP_CREATED",
"TENANT_PRINCIPAL_OWNERSHIP_IN_PROGRESS",
"TENANT_PRINCIPAL_OWNERSHIP_FAILED",
"BILLING_OWNER_CREATED",
"BILLING_OWNER_IN_PROGRESS",
"BILLING_OWNER_FAILED",
name="fsmstates",
native_enum=False,
),
existing_nullable=False,
)
# ### end Alembic commands ###

View File

@ -1,4 +1,6 @@
import json
import random
import string
from secrets import token_hex, token_urlsafe
from uuid import uuid4
from flask import current_app as app
@ -54,6 +56,8 @@ from .models import (
TaskOrderBillingVerificationCSPResult,
TenantAdminOwnershipCSPPayload,
TenantAdminOwnershipCSPResult,
TenantAdminCredentialResetCSPPayload,
TenantAdminCredentialResetCSPResult,
TenantCSPPayload,
TenantCSPResult,
TenantPrincipalAppCSPPayload,
@ -933,6 +937,20 @@ class AzureCloudProvider(CloudProviderInterface):
f"azure application error during product purchase verification. {str(exc)}",
)
def create_tenant_admin_credential_reset(
self, payload: TenantAdminCredentialResetCSPPayload
):
graph_token = self._get_tenant_admin_token(
payload.tenant_id, self.graph_resource
)
if graph_token is None:
raise AuthenticationException(
"Could not resolve graph token for tenant admin"
)
self._update_active_directory_user_password_profile(graph_token, payload)
def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload):
mgmt_token = self._get_elevated_management_token(payload.tenant_id)
@ -1581,6 +1599,62 @@ class AzureCloudProvider(CloudProviderInterface):
f"azure application error updating active directory user email. {str(exc)}",
)
def _update_active_directory_user_password_profile(self, graph_token, payload):
request_body = {
"passwordProfile": {
"forceChangePasswordNextSignIn": True,
"forceChangePasswordNextSignInWithMfa": False,
"password": payload.new_password
or "".join(random.choice(string.ascii_letters) for i in range(15)),
}
}
auth_header = {
"Authorization": f"Bearer {graph_token}",
}
url = f"{self.graph_resource}v1.0/users/{payload.user_id}"
try:
result = self.sdk.requests.patch(
url, headers=auth_header, json=request_body, timeout=30
)
result.raise_for_status()
if result.ok:
return True
else:
raise UserProvisioningException(
f"Failed update user password profile: {response.json()}"
)
except self.sdk.requests.exceptions.ConnectionError:
app.logger.error(
f"Could not update active directory user password profile. Connection Error",
exc_info=1,
)
raise ConnectionException(
"connection error updating active directory user password profile"
)
except self.sdk.requests.exceptions.Timeout:
app.logger.error(
f"Could not update active directory user password profile. Request timed out.",
exc_info=1,
)
raise ConnectionException(
"timout error updating active directory user password profile"
)
except self.sdk.requests.exceptions.HTTPError as exc:
app.logger.error(
result.status_code,
"azure application error updating active directory user password profile",
exc_info=1,
)
raise UnknownServerException(
result.status_code,
f"azure application error updating active directory user password profile. {str(exc)}",
)
def create_user_role(self, payload: UserRoleCSPPayload):
graph_token = self._get_tenant_principal_token(payload.tenant_id)
if graph_token is None:

View File

@ -51,6 +51,8 @@ from .models import (
TaskOrderBillingVerificationCSPResult,
TenantAdminOwnershipCSPPayload,
TenantAdminOwnershipCSPResult,
TenantAdminCredentialResetCSPPayload,
TenantAdminCredentialResetCSPResult,
TenantCSPPayload,
TenantCSPResult,
TenantPrincipalAppCSPPayload,
@ -335,6 +337,14 @@ class MockCloudProvider(CloudProviderInterface):
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
return TenantAdminOwnershipCSPResult(**dict(id="admin_owner_assignment_id"))
def create_tenant_admin_credential_reset(
self, payload: TenantAdminCredentialResetCSPPayload
):
self._maybe_raise(self.NETWORK_FAILURE_PCT, self.NETWORK_EXCEPTION)
self._maybe_raise(self.SERVER_FAILURE_PCT, self.SERVER_EXCEPTION)
self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION)
return TenantAdminCredentialResetCSPResult()
def create_tenant_principal_ownership(
self, payload: TenantPrincipalOwnershipCSPPayload
):

View File

@ -244,6 +244,15 @@ class TenantAdminOwnershipCSPResult(AliasModel):
fields = {"admin_owner_assignment_id": "id"}
class TenantAdminCredentialResetCSPPayload(BaseCSPPayload):
user_id: str
new_password: Optional[str]
class TenantAdminCredentialResetCSPResult(AliasModel):
pass
class TenantPrincipalOwnershipCSPPayload(BaseCSPPayload):
principal_id: str

View File

@ -29,6 +29,7 @@ class AzureStages(Enum):
TENANT_ADMIN_OWNERSHIP = "tenant admin ownership"
TENANT_PRINCIPAL_OWNERSHIP = "tenant principial ownership"
BILLING_OWNER = "billing owner"
TENANT_ADMIN_CREDENTIAL_RESET = "tenant admin credential reset"
def _build_csp_states(csp_stages):

View File

@ -56,6 +56,8 @@ from atat.domain.csp.cloud.models import (
TaskOrderBillingVerificationCSPResult,
TenantAdminOwnershipCSPPayload,
TenantAdminOwnershipCSPResult,
TenantAdminCredentialResetCSPPayload,
TenantAdminCredentialResetCSPResult,
TenantCSPPayload,
TenantCSPResult,
TenantPrincipalAppCSPPayload,
@ -1431,6 +1433,38 @@ def test_update_active_directory_user_email(mock_azure: AzureCloudProvider):
assert result
def test_update_active_directory_user_password_profile(mock_azure: AzureCloudProvider):
mock_result = Mock()
mock_result.ok = True
mock_http_error_resp = mock_requests_response(
status=500,
raise_for_status=mock_azure.sdk.requests.exceptions.HTTPError(
"500 Server Error"
),
)
mock_azure.sdk.requests.patch.side_effect = [
mock_azure.sdk.requests.exceptions.ConnectionError,
mock_azure.sdk.requests.exceptions.Timeout,
mock_http_error_resp,
mock_result,
]
payload = TenantAdminCredentialResetCSPPayload(
tenant_id="6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4",
user_id="admin",
new_password="asdfghjkl", # pragma: allowlist secret
)
with pytest.raises(ConnectionException):
mock_azure._update_active_directory_user_password_profile("token", payload)
with pytest.raises(ConnectionException):
mock_azure._update_active_directory_user_password_profile("token", payload)
with pytest.raises(UnknownServerException, match=r".*500 Server Error.*"):
mock_azure._update_active_directory_user_password_profile("token", payload)
result = mock_azure._update_active_directory_user_password_profile("token", payload)
assert result
def test_create_user(mock_azure: AzureCloudProvider):
with patch.object(
AzureCloudProvider,

View File

@ -305,6 +305,7 @@ def test_fsm_transition_start(
FSMStates.INITIAL_MGMT_GROUP_CREATED,
FSMStates.INITIAL_MGMT_GROUP_VERIFICATION_CREATED,
FSMStates.TENANT_ADMIN_OWNERSHIP_CREATED,
FSMStates.TENANT_ADMIN_CREDENTIAL_RESET_CREATED,
FSMStates.TENANT_PRINCIPAL_OWNERSHIP_CREATED,
FSMStates.BILLING_OWNER_CREATED,
FSMStates.COMPLETED,