Celery wrapper for creating a user.

This commit is contained in:
dandds 2020-02-02 13:58:41 -05:00
parent b1c6dd5ad0
commit 6b8d9d1d65
8 changed files with 154 additions and 105 deletions

View File

@ -65,6 +65,15 @@ class ApplicationRoles(object):
except NoResultFound:
raise NotFoundError("application_role")
@classmethod
def get_many(cls, ids):
return (
db.session.query(ApplicationRole)
.filter(ApplicationRole.id.in_(ids))
.filter(ApplicationRole.status != ApplicationRoleStatus.DISABLED)
.all()
)
@classmethod
def update_permission_sets(cls, application_role, new_perm_sets_names):
application_role.permission_sets = ApplicationRoles._permission_sets_for_names(

View File

@ -51,6 +51,8 @@ from .models import (
TenantPrincipalCSPResult,
TenantPrincipalOwnershipCSPPayload,
TenantPrincipalOwnershipCSPResult,
UserCSPPayload,
UserCSPResult,
)
@ -475,6 +477,11 @@ class MockCloudProvider(CloudProviderInterface):
id=f"{AZURE_MGMNT_PATH}{payload.management_group_name}"
)
def create_user(self, payload: UserCSPPayload):
self._maybe_raise(self.UNAUTHORIZED_RATE, GeneralCSPException)
return UserCSPResult(id=str(uuid4()))
def get_credentials(self, scope="portfolio", tenant_id=None):
return self.root_creds()

View File

@ -1,6 +1,7 @@
from secrets import token_urlsafe
from typing import Dict, List, Optional
import re
from uuid import uuid4
import re
from pydantic import BaseModel, validator, root_validator
@ -478,13 +479,10 @@ class ProductPurchaseVerificationCSPResult(AliasModel):
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
password: Optional[str]
@property
def user_principal_name(self):
@ -494,6 +492,10 @@ class UserCSPPayload(BaseCSPPayload):
def mail_nickname(self):
return self.display_name.replace(" ", ".").lower()
@validator("password", pre=True, always=True)
def supply_password_default(cls, password):
return password or token_urlsafe(16)
class UserCSPResult(AliasModel):
id: str

View File

@ -3,16 +3,16 @@ import pendulum
from atst.database import db
from atst.queue import celery
from atst.models import EnvironmentRole, JobFailure
from atst.models import JobFailure
from atst.domain.csp.cloud.exceptions import GeneralCSPException
from atst.domain.csp.cloud import CloudProviderInterface
from atst.domain.applications import Applications
from atst.domain.environments import Environments
from atst.domain.portfolios import Portfolios
from atst.domain.environment_roles import EnvironmentRoles
from atst.models.utils import claim_for_update
from atst.domain.application_roles import ApplicationRoles
from atst.models.utils import claim_for_update, claim_many_for_update
from atst.utils.localization import translate
from atst.domain.csp.cloud.models import ApplicationCSPPayload
from atst.domain.csp.cloud.models import ApplicationCSPPayload, UserCSPPayload
class RecordFailure(celery.Task):
@ -75,6 +75,34 @@ def do_create_application(csp: CloudProviderInterface, application_id=None):
db.session.commit()
def do_create_user(csp: CloudProviderInterface, application_role_ids=None):
if not application_role_ids:
return
app_roles = ApplicationRoles.get_many(application_role_ids)
with claim_many_for_update(app_roles) as app_roles:
if any([ar.cloud_id for ar in app_roles]):
return
csp_details = app_roles[0].application.portfolio.csp_data
user = app_roles[0].user
payload = UserCSPPayload(
tenant_id=csp_details.get("tenant_id"),
tenant_host_name=csp_details.get("domain_name"),
display_name=user.full_name,
email=user.email,
)
result = csp.create_user(payload)
for app_role in app_roles:
app_role.cloud_id = result.id
db.session.add(app_role)
db.session.commit()
def do_create_environment(csp: CloudProviderInterface, environment_id=None):
environment = Environments.get(environment_id)
@ -128,21 +156,6 @@ def render_email(template_path, context):
return app.jinja_env.get_template(template_path).render(context)
def do_provision_user(csp: CloudProviderInterface, environment_role_id=None):
environment_role = EnvironmentRoles.get_by_id(environment_role_id)
with claim_for_update(environment_role) as environment_role:
credentials = environment_role.environment.csp_credentials
csp_user_id = csp.create_or_update_user(
credentials, environment_role, environment_role.role
)
environment_role.csp_user_id = csp_user_id
environment_role.status = EnvironmentRole.Status.COMPLETED
db.session.add(environment_role)
db.session.commit()
def do_work(fn, task, csp, **kwargs):
try:
fn(csp, **kwargs)
@ -166,6 +179,13 @@ def create_application(self, application_id=None):
do_work(do_create_application, self, app.csp.cloud, application_id=application_id)
@celery.task(bind=True, base=RecordFailure)
def create_user(self, application_role_ids=None):
do_work(
do_create_user, self, app.csp.cloud, application_role_ids=application_role_ids
)
@celery.task(bind=True, base=RecordFailure)
def create_environment(self, environment_id=None):
do_work(do_create_environment, self, app.csp.cloud, environment_id=environment_id)
@ -178,13 +198,6 @@ def create_atat_admin_user(self, environment_id=None):
)
@celery.task(bind=True)
def provision_user(self, environment_role_id=None):
do_work(
do_provision_user, self, app.csp.cloud, environment_role_id=environment_role_id
)
@celery.task(bind=True)
def dispatch_provision_portfolio(self):
"""
@ -200,6 +213,12 @@ def dispatch_create_application(self):
create_application.delay(application_id=application_id)
@celery.task(bind=True)
def dispatch_create_user(self):
for application_role_ids in ApplicationRoles.get_pending_creation():
create_user.delay(application_role_ids=application_role_ids)
@celery.task(bind=True)
def dispatch_create_environment(self):
for environment_id in Environments.get_environments_pending_creation(
@ -214,11 +233,3 @@ def dispatch_create_atat_admin_user(self):
pendulum.now()
):
create_atat_admin_user.delay(environment_id=environment_id)
@celery.task(bind=True)
def dispatch_provision_user(self):
for (
environment_role_id
) in EnvironmentRoles.get_environment_roles_pending_creation():
provision_user.delay(environment_role_id=environment_role_id)

View File

@ -23,8 +23,8 @@ def update_celery(celery, app):
"task": "atst.jobs.dispatch_create_atat_admin_user",
"schedule": 60,
},
"beat-dispatch_provision_user": {
"task": "atst.jobs.dispatch_provision_user",
"beat-dispatch_create_user": {
"task": "atst.jobs.dispatch_create_user",
"schedule": 60,
},
}

View File

@ -7,6 +7,7 @@ from atst.domain.csp.cloud.models import (
KeyVaultCredentials,
ManagementGroupCSPPayload,
ManagementGroupCSPResponse,
UserCSPPayload,
)
@ -97,3 +98,26 @@ def test_KeyVaultCredentials_enforce_root_creds():
assert KeyVaultCredentials(
root_tenant_id="an id", root_sp_client_id="C3PO", root_sp_key="beep boop"
)
user_payload = {
"tenant_id": "123",
"display_name": "Han Solo",
"tenant_host_name": "rebelalliance.com",
"email": "han@moseisley.cantina",
}
def test_UserCSPPayload_mail_nickname():
payload = UserCSPPayload(**user_payload)
assert payload.mail_nickname == f"han.solo"
def test_UserCSPPayload_user_principal_name():
payload = UserCSPPayload(**user_payload)
assert payload.user_principal_name == f"han.solo@rebelalliance.com"
def test_UserCSPPayload_password():
payload = UserCSPPayload(**user_payload)
assert payload.password

View File

@ -153,3 +153,12 @@ def test_get_pending_creation():
expected_ids = [[role_one.id, role_two.id], [role_three.id], [role_four.id]]
# Sort them to produce the same order.
assert sorted(app_ids) == sorted(expected_ids)
def test_get_many():
ar1 = ApplicationRoleFactory.create()
ar2 = ApplicationRoleFactory.create()
ApplicationRoleFactory.create()
result = ApplicationRoles.get_many([ar1.id, ar2.id])
assert result == [ar1, ar2]

View File

@ -5,16 +5,17 @@ from unittest.mock import Mock
from atst.domain.csp.cloud import MockCloudProvider
from atst.domain.portfolios import Portfolios
from atst.models import ApplicationRoleStatus
from atst.jobs import (
RecordFailure,
dispatch_create_environment,
dispatch_create_application,
dispatch_create_user,
dispatch_create_atat_admin_user,
dispatch_provision_portfolio,
dispatch_provision_user,
create_environment,
do_provision_user,
do_create_user,
do_provision_portfolio,
do_create_environment,
do_create_application,
@ -27,6 +28,7 @@ from tests.factories import (
PortfolioStateMachineFactory,
ApplicationFactory,
ApplicationRoleFactory,
UserFactory,
)
from atst.models import CSPRole, EnvironmentRole, ApplicationRoleStatus, JobFailure
@ -123,6 +125,30 @@ def test_create_application_job_is_idempotent(csp):
csp.create_application.assert_not_called()
def test_create_user_job(session, csp):
portfolio = PortfolioFactory.create(
csp_data={
"tenant_id": str(uuid4()),
"domain_name": "rebelalliance.onmicrosoft.com",
}
)
application = ApplicationFactory.create(portfolio=portfolio, cloud_id="321")
user = UserFactory.create(
first_name="Han", last_name="Solo", email="han@example.com"
)
app_role = ApplicationRoleFactory.create(
application=application,
user=user,
status=ApplicationRoleStatus.ACTIVE,
cloud_id=None,
)
do_create_user(csp, [app_role.id])
session.refresh(app_role)
assert app_role.cloud_id
def test_create_atat_admin_user(csp, session):
environment = EnvironmentFactory.create(cloud_id="something")
do_create_atat_admin_user(csp, environment.id)
@ -178,6 +204,29 @@ def test_dispatch_create_application(monkeypatch):
mock.delay.assert_called_once_with(application_id=app.id)
def test_dispatch_create_user(monkeypatch):
application = ApplicationFactory.create(cloud_id="123")
user = UserFactory.create(
first_name="Han", last_name="Solo", email="han@example.com"
)
app_role = ApplicationRoleFactory.create(
application=application,
user=user,
status=ApplicationRoleStatus.ACTIVE,
cloud_id=None,
)
mock = Mock()
monkeypatch.setattr("atst.jobs.create_user", mock)
# When dispatch_create_user is called
dispatch_create_user.run()
# It should cause the create_user task to be called once
# with the application id
mock.delay.assert_called_once_with(application_role_ids=[app_role.id])
def test_dispatch_create_atat_admin_user(session, monkeypatch):
portfolio = PortfolioFactory.create(
applications=[
@ -237,68 +286,6 @@ def test_create_environment_no_dupes(session, celery_app, celery_worker):
assert environment.claimed_until == None
def test_dispatch_provision_user(csp, session, celery_app, celery_worker, monkeypatch):
# Given that I have four environment roles:
# (A) one of which has a completed status
# (B) one of which has an environment that has not been provisioned
# (C) one of which is pending, has a provisioned environment but an inactive application role
# (D) one of which is pending, has a provisioned environment and has an active application role
provisioned_environment = EnvironmentFactory.create(
cloud_id="cloud_id", root_user_info={}
)
unprovisioned_environment = EnvironmentFactory.create()
_er_a = EnvironmentRoleFactory.create(
environment=provisioned_environment, status=EnvironmentRole.Status.COMPLETED
)
_er_b = EnvironmentRoleFactory.create(
environment=unprovisioned_environment, status=EnvironmentRole.Status.PENDING
)
_er_c = EnvironmentRoleFactory.create(
environment=unprovisioned_environment,
status=EnvironmentRole.Status.PENDING,
application_role=ApplicationRoleFactory(status=ApplicationRoleStatus.PENDING),
)
er_d = EnvironmentRoleFactory.create(
environment=provisioned_environment,
status=EnvironmentRole.Status.PENDING,
application_role=ApplicationRoleFactory(status=ApplicationRoleStatus.ACTIVE),
)
mock = Mock()
monkeypatch.setattr("atst.jobs.provision_user", mock)
# When I dispatch the user provisioning task
dispatch_provision_user.run()
# I expect it to dispatch only one call, to EnvironmentRole D
mock.delay.assert_called_once_with(environment_role_id=er_d.id)
def test_do_provision_user(csp, session):
# Given that I have an EnvironmentRole with a provisioned environment
credentials = MockCloudProvider(())._auth_credentials
provisioned_environment = EnvironmentFactory.create(
cloud_id="cloud_id", root_user_info={"credentials": credentials}
)
environment_role = EnvironmentRoleFactory.create(
environment=provisioned_environment,
status=EnvironmentRole.Status.PENDING,
role="ADMIN",
)
# When I call the user provisoning task
do_provision_user(csp=csp, environment_role_id=environment_role.id)
session.refresh(environment_role)
# I expect that the CSP create_or_update_user method will be called
csp.create_or_update_user.assert_called_once_with(
credentials, environment_role, CSPRole.ADMIN
)
# I expect that the EnvironmentRole now has a csp_user_id
assert environment_role.csp_user_id
def test_dispatch_provision_portfolio(
csp, session, portfolio, celery_app, celery_worker, monkeypatch
):