From a0d59aa9e431ecf8757b6eb9b161da52ed244c4a Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 28 Jan 2020 09:00:06 -0500 Subject: [PATCH 01/11] portfolio provisioning. create product purchase and verification stages --- atst/domain/csp/cloud/azure_cloud_provider.py | 62 +++++++++++++++++++ atst/domain/csp/cloud/mock_cloud_provider.py | 35 +++++++++++ atst/domain/csp/cloud/models.py | 47 ++++++++++++++ atst/models/mixins/state_machines.py | 2 + 4 files changed, 146 insertions(+) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 84a9238c..2b50028f 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -19,6 +19,10 @@ from .models import ( BillingProfileVerificationCSPResult, KeyVaultCredentials, ManagementGroupCSPResponse, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -493,6 +497,64 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) + def create_product_purchase( + self, payload: ProductPurchaseCSPPayload + ): + sp_token = self._get_sp_token(payload.creds) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for aad premium product purchase" + ) + + create_product_purchase_body = payload.dict(by_alias=True) + create_product_purchase_headers = { + "Authorization": f"Bearer {sp_token}", + } + + product_purchase_url = f"https://management.azure.com//providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/products?api-version=2019-10-01-preview" + + result = self.sdk.requests.post( + product_purchase_url, + json=create_product_purchase_body, + headers=create_product_purchase_headers, + ) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(ProductPurchaseCSPResult(**result.headers)) + elif result.status_code == 200: + # NB: Swagger docs imply call can sometimes resolve immediately + return self._ok(ProductPurchaseCSPResult(**result.json())) + else: + return self._error(result.json()) + + + def create_product_purchase_verification( + self, payload: ProductPurchaseVerificationCSPPayload + ): + sp_token = self._get_sp_token(payload.creds) + if sp_token is None: + raise AuthenticationException( + "Could not resolve token for task order billing validation" + ) + + auth_header = { + "Authorization": f"Bearer {sp_token}", + } + + result = self.sdk.requests.get( + payload.product_purchase_verify_url, headers=auth_header + ) + + if result.status_code == 202: + # 202 has location/retry after headers + return self._ok(ProductPurchaseCSPResult(**result.headers)) + elif result.status_code == 200: + return self._ok(ProductPurchaseVerificationCSPResult(**result.json())) + else: + return self._error(result.json()) + + def create_remote_admin(self, creds, tenant_details): # create app/service principal within tenant, with name constructed from tenant details # assign principal global admin diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 10d62e15..bd29f796 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -26,6 +26,10 @@ from .models import ( BillingProfileCreationCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -277,6 +281,37 @@ class MockCloudProvider(CloudProviderInterface): } ) + + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): + 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 ProductPurchaseCSPResult( + **{ + "name": "TO1:CLIN001", + "properties": { + "amount": 1000.0, + "endDate": "2020-03-01T00:00:00+00:00", + "startDate": "2020-01-01T00:00:00+00:00", + }, + "type": "Microsoft.Billing/billingAccounts/billingProfiles/billingInstructions", + } + ) + + def create_product_purchase_verification( + self, payload: ProductPurchaseVerificationCSPPayload + ): + 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 ProductPurchaseVerificationCSPResult( + **{ + } + ) + + def create_or_update_user(self, auth_credentials, user_info, csp_role_id): self._authorize(auth_credentials) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index b4ff9232..ad9d171e 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -341,3 +341,50 @@ class KeyVaultCredentials(BaseModel): ) return values + +class AadPremiumProductParameter(AliasModel): + type: str + sku: str + quantity: int + productProperties: Dict + + #{ + # "type": "string", + # "sku": "string", + # "quantity": 0, + # "productProperties": { + # "beneficiaryTenantId": "string" + # } + #} + +class ProductPurchaseCSPPayload(BaseCSPPayload): + billing_account_name: str + billing_profile_name: str + parameters: List[AadPremiumProductParameter] + +class ProductPurchaseCSPResult(AliasModel): + product_purchase_verify_url: str + product_purchase_retry_after: int + + class Config: + fields = { + "product_purchase_verify_url": "Location", + "product_purchase_retry_after": "Retry-After", + } + + +class ProductPurchaseVerificationCSPPayload(BaseCSPPayload): + product_purchase_verify_url: str + + +class ProductPurchaseVerificationCSPResult(AliasModel): + billing_profile_id: str + billing_profile_name: str + billing_profile_enabled_plan_details: BillingProfileEnabledPlanDetails + + class Config: + fields = { + "billing_profile_id": "id", + "billing_profile_name": "name", + "billing_profile_enabled_plan_details": "properties", + } diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index 37682e5b..1db257df 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -17,6 +17,8 @@ class AzureStages(Enum): TASK_ORDER_BILLING_CREATION = "task order billing creation" TASK_ORDER_BILLING_VERIFICATION = "task order billing verification" BILLING_INSTRUCTION = "billing instruction" + PRODUCT_PURCHASE = "purchase aad premium product" + PRODUCT_PURCHASE_VERIFICATION = "purchase aad premium product verification" def _build_csp_states(csp_stages): From 5223fda8f8d6eb8fb86f307781055f671a0e72a3 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 28 Jan 2020 12:02:38 -0500 Subject: [PATCH 02/11] product purchase mock function correct args --- atst/domain/csp/cloud/mock_cloud_provider.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index bd29f796..d69b7ff0 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -288,15 +288,7 @@ class MockCloudProvider(CloudProviderInterface): self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) return ProductPurchaseCSPResult( - **{ - "name": "TO1:CLIN001", - "properties": { - "amount": 1000.0, - "endDate": "2020-03-01T00:00:00+00:00", - "startDate": "2020-01-01T00:00:00+00:00", - }, - "type": "Microsoft.Billing/billingAccounts/billingProfiles/billingInstructions", - } + **{"Location": "https://somelocation", "Retry-After": "10"} ) def create_product_purchase_verification( From d042282ca0c2a893d686754883906c9dfbddc4b3 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Tue, 28 Jan 2020 12:06:34 -0500 Subject: [PATCH 03/11] test state machine update expected states --- tests/domain/test_portfolio_state_machine.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/domain/test_portfolio_state_machine.py b/tests/domain/test_portfolio_state_machine.py index 2e412653..cdd206b8 100644 --- a/tests/domain/test_portfolio_state_machine.py +++ b/tests/domain/test_portfolio_state_machine.py @@ -104,6 +104,8 @@ def test_fsm_transition_start(mock_cloud_provider, portfolio: Portfolio): FSMStates.TASK_ORDER_BILLING_CREATION_CREATED, FSMStates.TASK_ORDER_BILLING_VERIFICATION_CREATED, FSMStates.BILLING_INSTRUCTION_CREATED, + FSMStates.PRODUCT_PURCHASE_CREATED, + FSMStates.PRODUCT_PURCHASE_VERIFICATION_CREATED, ] # Should source all creds for portfolio? might be easier to manage than per-step specific ones From 7493b9c3d6d0ec7844ac43a01d626972d508df9d Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 30 Jan 2020 14:48:16 -0500 Subject: [PATCH 04/11] premium product purchase unit tests. --- atst/domain/csp/cloud/azure_cloud_provider.py | 20 +++- atst/domain/csp/cloud/mock_cloud_provider.py | 15 ++- atst/domain/csp/cloud/models.py | 27 +----- tests/domain/cloud/test_azure_csp.py | 92 +++++++++++++++++++ 4 files changed, 121 insertions(+), 33 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 2b50028f..b0e2de86 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -506,12 +506,21 @@ class AzureCloudProvider(CloudProviderInterface): "Could not resolve token for aad premium product purchase" ) - create_product_purchase_body = payload.dict(by_alias=True) + payload_as_dict = payload.dict(by_alias=True) + + create_product_purchase_body = { + "type": "AADPremium", + "sku": "AADP1", + "productProperties": { + "beneficiaryTenantId": payload_as_dict["productProperties"]["beneficiaryTenantId"], + }, + "quantity": payload_as_dict.get("quantity"), + } create_product_purchase_headers = { "Authorization": f"Bearer {sp_token}", } - product_purchase_url = f"https://management.azure.com//providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/products?api-version=2019-10-01-preview" + product_purchase_url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/products?api-version=2019-10-01-preview" result = self.sdk.requests.post( product_purchase_url, @@ -524,7 +533,7 @@ class AzureCloudProvider(CloudProviderInterface): return self._ok(ProductPurchaseCSPResult(**result.headers)) elif result.status_code == 200: # NB: Swagger docs imply call can sometimes resolve immediately - return self._ok(ProductPurchaseCSPResult(**result.json())) + return self._ok(ProductPurchaseVerificationCSPResult(**result.json())) else: return self._error(result.json()) @@ -535,7 +544,7 @@ class AzureCloudProvider(CloudProviderInterface): sp_token = self._get_sp_token(payload.creds) if sp_token is None: raise AuthenticationException( - "Could not resolve token for task order billing validation" + "Could not resolve token for aad premium product purchase validation" ) auth_header = { @@ -545,12 +554,13 @@ class AzureCloudProvider(CloudProviderInterface): result = self.sdk.requests.get( payload.product_purchase_verify_url, headers=auth_header ) + premium_purchase_date = result.json()["product"]["properties"]["purchaseDate"] if result.status_code == 202: # 202 has location/retry after headers return self._ok(ProductPurchaseCSPResult(**result.headers)) elif result.status_code == 200: - return self._ok(ProductPurchaseVerificationCSPResult(**result.json())) + return self._ok(ProductPurchaseVerificationCSPResult(premium_purchase_date=premium_purchase_date)) else: return self._error(result.json()) diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index d69b7ff0..4ef72fb9 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -282,15 +282,22 @@ class MockCloudProvider(CloudProviderInterface): ) - def create_product_purchase(self, payload: ProductPurchaseCSPPayload): + def create_product_purchase( + self, payload: ProductPurchaseCSPPayload + ): 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 ProductPurchaseCSPResult( - **{"Location": "https://somelocation", "Retry-After": "10"} + **dict( + product_purchase_verify_url="https://zombo.com", + product_purchase_retry_after=10, + ) ) + + def create_product_purchase_verification( self, payload: ProductPurchaseVerificationCSPPayload ): @@ -299,11 +306,9 @@ class MockCloudProvider(CloudProviderInterface): self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) return ProductPurchaseVerificationCSPResult( - **{ - } + **dict(premium_purchase_date="2020-01-30T18:57:05.981Z") ) - def create_or_update_user(self, auth_credentials, user_info, csp_role_id): self._authorize(auth_credentials) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index ad9d171e..d119b7eb 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -342,25 +342,15 @@ class KeyVaultCredentials(BaseModel): return values -class AadPremiumProductParameter(AliasModel): + +class ProductPurchaseCSPPayload(BaseCSPPayload): type: str sku: str quantity: int productProperties: Dict - - #{ - # "type": "string", - # "sku": "string", - # "quantity": 0, - # "productProperties": { - # "beneficiaryTenantId": "string" - # } - #} - -class ProductPurchaseCSPPayload(BaseCSPPayload): billing_account_name: str billing_profile_name: str - parameters: List[AadPremiumProductParameter] + class ProductPurchaseCSPResult(AliasModel): product_purchase_verify_url: str @@ -378,13 +368,4 @@ class ProductPurchaseVerificationCSPPayload(BaseCSPPayload): class ProductPurchaseVerificationCSPResult(AliasModel): - billing_profile_id: str - billing_profile_name: str - billing_profile_enabled_plan_details: BillingProfileEnabledPlanDetails - - class Config: - fields = { - "billing_profile_id": "id", - "billing_profile_name": "name", - "billing_profile_enabled_plan_details": "properties", - } + premium_purchase_date: str diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 39fa2f77..a59a51ea 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -18,6 +18,10 @@ from atst.domain.csp.cloud.models import ( BillingProfileTenantAccessCSPResult, BillingProfileVerificationCSPPayload, BillingProfileVerificationCSPResult, + ProductPurchaseCSPPayload, + ProductPurchaseCSPResult, + ProductPurchaseVerificationCSPPayload, + ProductPurchaseVerificationCSPResult, TaskOrderBillingCreationCSPPayload, TaskOrderBillingCreationCSPResult, TaskOrderBillingVerificationCSPPayload, @@ -335,6 +339,7 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider): result = mock_azure.create_task_order_billing_creation(payload) body: TaskOrderBillingCreationCSPResult = result.get("body") + assert ( body.task_order_billing_verify_url == "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview" @@ -435,3 +440,90 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): result = mock_azure.create_billing_instruction(payload) body: BillingInstructionCSPResult = result.get("body") assert body.reported_clin_name == "TO1:CLIN001" + + +def test_create_product_purchase(mock_azure: AzureCloudProvider): + mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = { + "accessToken": "TOKEN" + } + + mock_result = Mock() + mock_result.status_code = 202 + mock_result.headers = { + "Location": "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview", + "Retry-After": "10", + } + + mock_azure.sdk.requests.post.return_value = mock_result + + payload = ProductPurchaseCSPPayload( + **dict( + creds=creds, + type="AADPremium", + sku="AADP1", + productProperties={ + "beneficiaryTenantId": str(uuid4()), + }, + quantity=4, + billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", + billing_profile_name="KQWI-W2SU-BG7-TGB", + ) + ) + + result = mock_azure.create_product_purchase(payload) + body: ProductPurchaseCSPResult = result.get("body") + assert ( + body.product_purchase_verify_url == "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview" + ) + +def test_create_product_purchase_verification(mock_azure): + mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = { + "accessToken": "TOKEN" + } + + mock_result = Mock() + mock_result.status_code = 200 + mock_result.json.return_value = { + "status": "string", + "product": { + "id": "string", + "name": "string", + "type": "string", + "properties": { + "displayName": "string", + "purchaseDate": "2020-01-30T18:57:05.981Z", + "productTypeId": "string", + "productType": "string", + "status": "Active", + "endDate": "2020-01-30T18:57:05.981Z", + "billingFrequency": "OneTime", + "lastCharge": { + "currency": "string", + "value": 0 + }, + "lastChargeDate": "2020-01-30T18:57:05.981Z", + "quantity": 0, + "skuId": "string", + "skuDescription": "string", + "availabilityId": "string", + "parentProductId": "string", + "invoiceSectionId": "string", + "invoiceSectionDisplayName": "string", + "billingProfileId": "string", + "billingProfileDisplayName": "string" + } + } + } + + mock_azure.sdk.requests.get.return_value = mock_result + + payload = ProductPurchaseVerificationCSPPayload( + **dict( + creds=creds, + product_purchase_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", + ) + ) + + result = mock_azure.create_product_purchase_verification(payload) + body: ProductPurchaseVerificationCSPResult = result.get("body") + assert body.premium_purchase_date == "2020-01-30T18:57:05.981Z" From de992eeed8224e77d2059bf8063827747bbe641d Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 30 Jan 2020 14:50:16 -0500 Subject: [PATCH 05/11] premium product purchase code formatting. --- atst/domain/csp/cloud/azure_cloud_provider.py | 18 ++++++++++-------- atst/domain/csp/cloud/mock_cloud_provider.py | 7 +------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index b0e2de86..a83bef10 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -497,9 +497,7 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) - def create_product_purchase( - self, payload: ProductPurchaseCSPPayload - ): + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): sp_token = self._get_sp_token(payload.creds) if sp_token is None: raise AuthenticationException( @@ -508,11 +506,13 @@ class AzureCloudProvider(CloudProviderInterface): payload_as_dict = payload.dict(by_alias=True) - create_product_purchase_body = { + create_product_purchase_body = { "type": "AADPremium", "sku": "AADP1", "productProperties": { - "beneficiaryTenantId": payload_as_dict["productProperties"]["beneficiaryTenantId"], + "beneficiaryTenantId": payload_as_dict["productProperties"][ + "beneficiaryTenantId" + ], }, "quantity": payload_as_dict.get("quantity"), } @@ -537,7 +537,6 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) - def create_product_purchase_verification( self, payload: ProductPurchaseVerificationCSPPayload ): @@ -560,11 +559,14 @@ class AzureCloudProvider(CloudProviderInterface): # 202 has location/retry after headers return self._ok(ProductPurchaseCSPResult(**result.headers)) elif result.status_code == 200: - return self._ok(ProductPurchaseVerificationCSPResult(premium_purchase_date=premium_purchase_date)) + return self._ok( + ProductPurchaseVerificationCSPResult( + premium_purchase_date=premium_purchase_date + ) + ) else: return self._error(result.json()) - def create_remote_admin(self, creds, tenant_details): # create app/service principal within tenant, with name constructed from tenant details # assign principal global admin diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index 4ef72fb9..ddb856dd 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -281,10 +281,7 @@ class MockCloudProvider(CloudProviderInterface): } ) - - def create_product_purchase( - self, payload: ProductPurchaseCSPPayload - ): + def create_product_purchase(self, payload: ProductPurchaseCSPPayload): 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) @@ -296,8 +293,6 @@ class MockCloudProvider(CloudProviderInterface): ) ) - - def create_product_purchase_verification( self, payload: ProductPurchaseVerificationCSPPayload ): From 29b69a7d5d7e09931560a1c341af822281876806 Mon Sep 17 00:00:00 2001 From: Philip Kalinsky Date: Thu, 30 Jan 2020 16:50:32 -0500 Subject: [PATCH 06/11] code formatting --- atst/domain/csp/cloud/mock_cloud_provider.py | 1 - tests/domain/cloud/test_azure_csp.py | 51 ++++++++++---------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/atst/domain/csp/cloud/mock_cloud_provider.py b/atst/domain/csp/cloud/mock_cloud_provider.py index baf711d4..7a41cb31 100644 --- a/atst/domain/csp/cloud/mock_cloud_provider.py +++ b/atst/domain/csp/cloud/mock_cloud_provider.py @@ -321,7 +321,6 @@ class MockCloudProvider(CloudProviderInterface): self._maybe_raise(self.UNAUTHORIZED_RATE, self.AUTHORIZATION_EXCEPTION) return TenantAdminOwnershipCSPResult(**dict(id="admin_owner_assignment_id")) - def create_tenant_principal_ownership( self, payload: TenantPrincipalOwnershipCSPPayload ): diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 22b3cf8f..eef0a0d8 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -445,6 +445,7 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider): body: BillingInstructionCSPResult = result.get("body") assert body.reported_clin_name == "TO1:CLIN001" + def test_create_product_purchase(mock_azure: AzureCloudProvider): mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = { "accessToken": "TOKEN" @@ -464,9 +465,7 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): tenant_id="6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4", type="AADPremium", sku="AADP1", - productProperties={ - "beneficiaryTenantId": str(uuid4()), - }, + productProperties={"beneficiaryTenantId": str(uuid4()),}, quantity=4, billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", billing_profile_name="KQWI-W2SU-BG7-TGB", @@ -476,9 +475,11 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): result = mock_azure.create_product_purchase(payload) body: ProductPurchaseCSPResult = result.get("body") assert ( - body.product_purchase_verify_url == "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview" + body.product_purchase_verify_url + == "https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/patchBillingProfile_KQWI-W2SU-BG7-TGB:02715576-4118-466c-bca7-b1cd3169ff46?api-version=2019-10-01-preview" ) + def test_create_product_purchase_verification(mock_azure): mock_azure.sdk.adal.AuthenticationContext.return_value.context.acquire_token_with_client_credentials.return_value = { "accessToken": "TOKEN" @@ -493,29 +494,26 @@ def test_create_product_purchase_verification(mock_azure): "name": "string", "type": "string", "properties": { - "displayName": "string", - "purchaseDate": "2020-01-30T18:57:05.981Z", - "productTypeId": "string", - "productType": "string", - "status": "Active", - "endDate": "2020-01-30T18:57:05.981Z", - "billingFrequency": "OneTime", - "lastCharge": { - "currency": "string", - "value": 0 + "displayName": "string", + "purchaseDate": "2020-01-30T18:57:05.981Z", + "productTypeId": "string", + "productType": "string", + "status": "Active", + "endDate": "2020-01-30T18:57:05.981Z", + "billingFrequency": "OneTime", + "lastCharge": {"currency": "string", "value": 0}, + "lastChargeDate": "2020-01-30T18:57:05.981Z", + "quantity": 0, + "skuId": "string", + "skuDescription": "string", + "availabilityId": "string", + "parentProductId": "string", + "invoiceSectionId": "string", + "invoiceSectionDisplayName": "string", + "billingProfileId": "string", + "billingProfileDisplayName": "string", }, - "lastChargeDate": "2020-01-30T18:57:05.981Z", - "quantity": 0, - "skuId": "string", - "skuDescription": "string", - "availabilityId": "string", - "parentProductId": "string", - "invoiceSectionId": "string", - "invoiceSectionDisplayName": "string", - "billingProfileId": "string", - "billingProfileDisplayName": "string" - } - } + }, } mock_azure.sdk.requests.get.return_value = mock_result @@ -531,6 +529,7 @@ def test_create_product_purchase_verification(mock_azure): body: ProductPurchaseVerificationCSPResult = result.get("body") assert body.premium_purchase_date == "2020-01-30T18:57:05.981Z" + def test_create_tenant_principal_app(mock_azure: AzureCloudProvider): with patch.object( AzureCloudProvider, From 4fb9b88e1db0472301328250a5790cf2700dc0d9 Mon Sep 17 00:00:00 2001 From: tomdds Date: Thu, 30 Jan 2020 22:03:26 -0500 Subject: [PATCH 07/11] Add new purchase provisioning states to DB enum. --- .../50979d8ef680_add_aadp_purchase_steps.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 alembic/versions/50979d8ef680_add_aadp_purchase_steps.py diff --git a/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py b/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py new file mode 100644 index 00000000..cc0be2f3 --- /dev/null +++ b/alembic/versions/50979d8ef680_add_aadp_purchase_steps.py @@ -0,0 +1,251 @@ +"""Add AADP Purchase Steps + +Revision ID: 50979d8ef680 +Revises: cd7e3f9a5d64 +Create Date: 2020-01-30 17:00:27.916639 + +""" +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "50979d8ef680" # pragma: allowlist secret +down_revision = "cd7e3f9a5d64" # 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", + "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", + "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", + "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_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", + "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", + "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", + "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_nullable=False, + ) + # ### end Alembic commands ### From 6b8ea4192564b35811b030a0a6f1e365bf9d60eb Mon Sep 17 00:00:00 2001 From: tomdds Date: Thu, 30 Jan 2020 22:08:48 -0500 Subject: [PATCH 08/11] Make AADP purchase quantity configurable Also remove a few constant params from the payload model. --- atst/domain/csp/cloud/azure_cloud_provider.py | 11 +++-------- atst/domain/csp/cloud/models.py | 4 ---- tests/domain/cloud/test_azure_csp.py | 4 ---- tests/mock_azure.py | 1 + 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 3074e221..048b1d5b 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -99,6 +99,7 @@ class AzureCloudProvider(CloudProviderInterface): self.ps_client_id = config["POWERSHELL_CLIENT_ID"] self.owner_role_def_id = config["AZURE_OWNER_ROLE_DEF_ID"] self.graph_resource = config["AZURE_GRAPH_RESOURCE"] + self.default_aadp_qty = config["AZURE_AADP_QTY"] if azure_sdk_provider is None: self.sdk = AzureSDKProvider() @@ -528,17 +529,11 @@ class AzureCloudProvider(CloudProviderInterface): "Could not resolve token for aad premium product purchase" ) - payload_as_dict = payload.dict(by_alias=True) - create_product_purchase_body = { "type": "AADPremium", "sku": "AADP1", - "productProperties": { - "beneficiaryTenantId": payload_as_dict["productProperties"][ - "beneficiaryTenantId" - ], - }, - "quantity": payload_as_dict.get("quantity"), + "productProperties": {"beneficiaryTenantId": payload.tenant_id,}, + "quantity": self.default_aadp_qty, } create_product_purchase_headers = { "Authorization": f"Bearer {sp_token}", diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index aa24d027..4435093c 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -409,10 +409,6 @@ class KeyVaultCredentials(BaseModel): class ProductPurchaseCSPPayload(BaseCSPPayload): - type: str - sku: str - quantity: int - productProperties: Dict billing_account_name: str billing_profile_name: str diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index eef0a0d8..9e0c3cb0 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -463,10 +463,6 @@ def test_create_product_purchase(mock_azure: AzureCloudProvider): payload = ProductPurchaseCSPPayload( **dict( tenant_id="6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4", - type="AADPremium", - sku="AADP1", - productProperties={"beneficiaryTenantId": str(uuid4()),}, - quantity=4, billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", billing_profile_name="KQWI-W2SU-BG7-TGB", ) diff --git a/tests/mock_azure.py b/tests/mock_azure.py index 438ae855..ce85a396 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -12,6 +12,7 @@ AZURE_CONFIG = { "POWERSHELL_CLIENT_ID": "MOCK", "AZURE_OWNER_ROLE_DEF_ID": "MOCK", "AZURE_GRAPH_RESOURCE": "MOCK", + "AZURE_AADP_QTY": 5, } AUTH_CREDENTIALS = { From 73ddd323652d75b33bc06b016beb7a735321da64 Mon Sep 17 00:00:00 2001 From: tomdds Date: Fri, 31 Jan 2020 11:51:13 -0500 Subject: [PATCH 09/11] Only unpack AADP purchase date after verifying response code. --- atst/domain/csp/cloud/azure_cloud_provider.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 048b1d5b..6b058b11 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -572,12 +572,14 @@ class AzureCloudProvider(CloudProviderInterface): result = self.sdk.requests.get( payload.product_purchase_verify_url, headers=auth_header ) - premium_purchase_date = result.json()["product"]["properties"]["purchaseDate"] if result.status_code == 202: # 202 has location/retry after headers return self._ok(ProductPurchaseCSPResult(**result.headers)) elif result.status_code == 200: + premium_purchase_date = result.json()["product"]["properties"][ + "purchaseDate" + ] return self._ok( ProductPurchaseVerificationCSPResult( premium_purchase_date=premium_purchase_date From e672941259eee7bdfd1b9857803ff0af46dadeb9 Mon Sep 17 00:00:00 2001 From: tomdds Date: Fri, 31 Jan 2020 14:03:39 -0500 Subject: [PATCH 10/11] Source correct tenant id for root credentials --- atst/domain/csp/cloud/azure_cloud_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 6b058b11..fd273993 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -861,7 +861,7 @@ class AzureCloudProvider(CloudProviderInterface): def _get_root_provisioning_token(self): creds = self._source_creds() return self._get_sp_token( - creds.tenant_id, creds.root_sp_client_id, creds.root_sp_key + creds.root_tenant_id, creds.root_sp_client_id, creds.root_sp_key ) def _get_sp_token(self, tenant_id, client_id, secret_key): From bbed83d8974c9755c41fee199547c2dba660281d Mon Sep 17 00:00:00 2001 From: tomdds Date: Fri, 31 Jan 2020 15:38:03 -0500 Subject: [PATCH 11/11] Update AADP Purchase url and respose format to newer schema. --- atst/domain/csp/cloud/azure_cloud_provider.py | 6 +-- tests/domain/cloud/test_azure_csp.py | 44 ++++++++----------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index fd273993..2f571b59 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -539,7 +539,7 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - product_purchase_url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/products?api-version=2019-10-01-preview" + product_purchase_url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}/purchaseProduct?api-version=2019-10-01-preview" result = self.sdk.requests.post( product_purchase_url, @@ -577,9 +577,7 @@ class AzureCloudProvider(CloudProviderInterface): # 202 has location/retry after headers return self._ok(ProductPurchaseCSPResult(**result.headers)) elif result.status_code == 200: - premium_purchase_date = result.json()["product"]["properties"][ - "purchaseDate" - ] + premium_purchase_date = result.json()["properties"]["purchaseDate"] return self._ok( ProductPurchaseVerificationCSPResult( premium_purchase_date=premium_purchase_date diff --git a/tests/domain/cloud/test_azure_csp.py b/tests/domain/cloud/test_azure_csp.py index 9e0c3cb0..242f1fb3 100644 --- a/tests/domain/cloud/test_azure_csp.py +++ b/tests/domain/cloud/test_azure_csp.py @@ -484,32 +484,24 @@ def test_create_product_purchase_verification(mock_azure): mock_result = Mock() mock_result.status_code = 200 mock_result.json.return_value = { - "status": "string", - "product": { - "id": "string", - "name": "string", - "type": "string", - "properties": { - "displayName": "string", - "purchaseDate": "2020-01-30T18:57:05.981Z", - "productTypeId": "string", - "productType": "string", - "status": "Active", - "endDate": "2020-01-30T18:57:05.981Z", - "billingFrequency": "OneTime", - "lastCharge": {"currency": "string", "value": 0}, - "lastChargeDate": "2020-01-30T18:57:05.981Z", - "quantity": 0, - "skuId": "string", - "skuDescription": "string", - "availabilityId": "string", - "parentProductId": "string", - "invoiceSectionId": "string", - "invoiceSectionDisplayName": "string", - "billingProfileId": "string", - "billingProfileDisplayName": "string", - }, + "id": "/providers/Microsoft.Billing/billingAccounts/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME/invoiceSections/INVOICESECTION/products/29386e29-a025-faae-f70b-b1cbbc266600", + "name": "29386e29-a025-faae-f70b-b1cbbc266600", + "properties": { + "availabilityId": "C07TTFC7Q9XK", + "billingProfileId": "/providers/Microsoft.Billing/billingAccounts/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME", + "billingProfileDisplayName": "ATAT Billing Profile", + "endDate": "01/30/2021", + "invoiceSectionId": "/providers/Microsoft.Billing/billingAccounts/BILLINGACCOUNTNAME/billingProfiles/BILLINGPROFILENAME/invoiceSections/INVOICESECTION", + "invoiceSectionDisplayName": "ATAT Billing Profile", + "productType": "Azure Active Directory Premium P1", + "productTypeId": "C07TTFC7Q9XK", + "skuId": "0002", + "skuDescription": "Azure Active Directory Premium P1", + "purchaseDate": "01/31/2020", + "quantity": 5, + "status": "AutoRenew", }, + "type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections/products", } mock_azure.sdk.requests.get.return_value = mock_result @@ -523,7 +515,7 @@ def test_create_product_purchase_verification(mock_azure): result = mock_azure.create_product_purchase_verification(payload) body: ProductPurchaseVerificationCSPResult = result.get("body") - assert body.premium_purchase_date == "2020-01-30T18:57:05.981Z" + assert body.premium_purchase_date == "01/31/2020" def test_create_tenant_principal_app(mock_azure: AzureCloudProvider):