Celery wrapper for creating a user.
This commit is contained in:
parent
b1c6dd5ad0
commit
6b8d9d1d65
@ -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(
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
79
atst/jobs.py
79
atst/jobs.py
@ -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)
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
):
|
||||
|
Loading…
x
Reference in New Issue
Block a user