diff --git a/.secrets.baseline b/.secrets.baseline index 4e393738..45f10336 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$|^.*pgsslrootcert.yml$", "lines": null }, - "generated_at": "2020-02-10T21:40:38Z", + "generated_at": "2020-02-12T18:51:01Z", "plugins_used": [ { "base64_limit": 4.5, @@ -82,7 +82,7 @@ "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_secret": false, "is_verified": false, - "line_number": 43, + "line_number": 44, "type": "Secret Keyword" } ], diff --git a/README.md b/README.md index b8530135..accecd38 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,7 @@ To generate coverage reports for the Javascript tests: - `ASSETS_URL`: URL to host which serves static assets (such as a CDN). - `AZURE_ACCOUNT_NAME`: The name for the Azure blob storage account +- `AZURE_LOGIN_URL`: The URL used to login for an Azure instance. - `AZURE_STORAGE_KEY`: A valid secret key for the Azure blob storage account - `AZURE_TO_BUCKET_NAME`: The Azure blob storage container name for task order uploads - `BLOB_STORAGE_URL`: URL to Azure blob storage container. diff --git a/atst/app.py b/atst/app.py index 04aed44d..1499671c 100644 --- a/atst/app.py +++ b/atst/app.py @@ -186,6 +186,7 @@ def map_config(config): # with a Beat job once a day) "CELERY_RESULT_EXPIRES": 0, "CELERY_RESULT_EXTENDED": True, + "OFFICE_365_DOMAIN": "onmicrosoft.com", "CONTRACT_START_DATE": datetime.strptime( config.get("default", "CONTRACT_START_DATE"), "%Y-%m-%d" ).date(), diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 86afc69b..679d5cbd 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -331,6 +331,7 @@ class AzureCloudProvider(CloudProviderInterface): timeout=30, ) result.raise_for_status() + except self.sdk.requests.exceptions.ConnectionError: app.logger.error( f"Could not create tenant. Connection Error", exc_info=1, @@ -354,10 +355,7 @@ class AzureCloudProvider(CloudProviderInterface): result_dict = result.json() tenant_id = result_dict.get("tenantId") - tenant_admin_username = ( - f"{payload.user_id}@{payload.domain_name}.onmicrosoft.com" - ) - + tenant_admin_username = f"{payload.user_id}@{payload.domain_name}.{self.config.get('OFFICE_365_DOMAIN')}" self.update_tenant_creds( tenant_id, KeyVaultCredentials( @@ -366,6 +364,7 @@ class AzureCloudProvider(CloudProviderInterface): tenant_admin_password=payload.password, ), ) + return TenantCSPResult(domain_name=payload.domain_name, **result_dict) def create_billing_profile_creation( diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 859158a0..27f2c9c7 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -4,6 +4,7 @@ from typing import Dict, List, Optional from uuid import uuid4 import re +from flask import current_app as app from pydantic import BaseModel, validator, root_validator from atst.utils import snake_to_camel @@ -526,7 +527,7 @@ class UserMixin(BaseModel): @property def user_principal_name(self): - return f"{self.mail_nickname}@{self.tenant_host_name}.onmicrosoft.com" + return f"{self.mail_nickname}@{self.tenant_host_name}.{app.config.get('OFFICE_365_DOMAIN')}" @property def mail_nickname(self): diff --git a/atst/forms/data.py b/atst/forms/data.py index bb728686..ea2c1c8a 100644 --- a/atst/forms/data.py +++ b/atst/forms/data.py @@ -10,6 +10,10 @@ SERVICE_BRANCHES = [ translate("forms.portfolio.defense_component.choices.marine_corps"), ), ("navy", translate("forms.portfolio.defense_component.choices.navy")), + ("space_force", translate("forms.portfolio.defense_component.choices.space_force")), + ("ccmd_js", translate("forms.portfolio.defense_component.choices.ccmd_js")), + ("dafa", translate("forms.portfolio.defense_component.choices.dafa")), + ("osd_psas", translate("forms.portfolio.defense_component.choices.osd_psas")), ("other", translate("forms.portfolio.defense_component.choices.other")), ] diff --git a/atst/jobs.py b/atst/jobs.py index 0d462f0f..77a8c2f6 100644 --- a/atst/jobs.py +++ b/atst/jobs.py @@ -18,6 +18,7 @@ from atst.domain.environments import Environments from atst.domain.environment_roles import EnvironmentRoles from atst.domain.portfolios import Portfolios from atst.models import CSPRole, JobFailure +from atst.models.mixins.state_machines import FSMStates from atst.domain.task_orders import TaskOrders from atst.models.utils import claim_for_update, claim_many_for_update from atst.queue import celery @@ -177,10 +178,30 @@ def do_work(fn, task, csp, **kwargs): raise task.retry(exc=e) +def send_PPOC_email(portfolio_dict): + ppoc_email = portfolio_dict.get("password_recovery_email_address") + user_id = portfolio_dict.get("user_id") + domain_name = portfolio_dict.get("domain_name") + + send_mail( + recipients=[ppoc_email], + subject=translate("email.portfolio_ready.subject"), + body=translate( + "email.portfolio_ready.body", + { + "password_reset_address": app.config.get("AZURE_LOGIN_URL"), + "username": f"{user_id}@{domain_name}.{app.config.get('OFFICE_365_DOMAIN')}", + }, + ), + ) + + def do_provision_portfolio(csp: CloudProviderInterface, portfolio_id=None): portfolio = Portfolios.get_for_update(portfolio_id) fsm = Portfolios.get_or_create_state_machine(portfolio) fsm.trigger_next_transition(csp_data=portfolio.to_dictionary()) + if fsm.current_state == FSMStates.COMPLETED: + send_PPOC_email(portfolio.to_dictionary()) @celery.task(bind=True, base=RecordFailure) diff --git a/atst/models/mixins/state_machines.py b/atst/models/mixins/state_machines.py index cbc2fc8b..8c941ffe 100644 --- a/atst/models/mixins/state_machines.py +++ b/atst/models/mixins/state_machines.py @@ -58,6 +58,18 @@ def _build_transitions(csp_stages): transitions = [] states = [] for stage_i, csp_stage in enumerate(csp_stages): + # the last CREATED stage has a transition to COMPLETED + if stage_i == len(csp_stages) - 1: + transitions.append( + dict( + trigger="complete", + source=compose_state( + list(csp_stages)[stage_i], StageStates.CREATED + ), + dest=FSMStates.COMPLETED, + ) + ) + for state in StageStates: states.append( dict( diff --git a/atst/models/portfolio_state_machine.py b/atst/models/portfolio_state_machine.py index e77ab553..848d2b03 100644 --- a/atst/models/portfolio_state_machine.py +++ b/atst/models/portfolio_state_machine.py @@ -161,6 +161,15 @@ class PortfolioStateMachine( ) elif state_obj.is_CREATED: + # if last CREATED state then transition to COMPLETED + if list(AzureStages)[-1].name == state_obj.name.split("_CREATED")[ + 0 + ] and "complete" in self.machine.get_triggers(state_obj.name): + app.logger.info( + "last stage completed. transitioning to COMPLETED state" + ) + self.trigger("complete", **kwargs) + # the create trigger for the next stage should be in the available # triggers for the current state create_trigger = next( diff --git a/config/base.ini b/config/base.ini index 727172d8..3aa9ad86 100644 --- a/config/base.ini +++ b/config/base.ini @@ -4,6 +4,7 @@ AZURE_AADP_QTY=5 AZURE_ACCOUNT_NAME AZURE_CLIENT_ID AZURE_GRAPH_RESOURCE="https://graph.microsoft.com/" +AZURE_LOGIN_URL="https://portal.azure.com/" AZURE_POLICY_LOCATION=policies AZURE_POWERSHELL_CLIENT_ID AZURE_ROLE_DEF_ID_BILLING_READER="fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64" diff --git a/js/components/upload_input.js b/js/components/upload_input.js index 4f9f06fc..8bf806e9 100644 --- a/js/components/upload_input.js +++ b/js/components/upload_input.js @@ -72,7 +72,7 @@ export default { const uploader = await this.getUploader() const response = await uploader.upload(file) if (uploadResponseOkay(response)) { - this.attachment = e.target.value + this.attachment = file.name this.objectName = uploader.objectName this.$refs.attachmentFilename.value = file.name this.$refs.attachmentObjectName.value = response.objectName diff --git a/templates/components/upload_input.html b/templates/components/upload_input.html index bd4cd73c..b4292284 100644 --- a/templates/components/upload_input.html +++ b/templates/components/upload_input.html @@ -43,7 +43,6 @@ :id="name" :name="name" aria-label="Task Order Upload" - v-bind:value="attachment" type="file"> diff --git a/templates/navigation/global_sidenav.html b/templates/navigation/global_sidenav.html index 3ec44a5d..8dc34b35 100644 --- a/templates/navigation/global_sidenav.html +++ b/templates/navigation/global_sidenav.html @@ -21,10 +21,10 @@