import flask from flask import flash from webargs import fields from webargs.flaskparser import use_kwargs from atlas_um import pgdb, tasks from atlas_um.auth import claims_required, current_user from atlas_um.auth.claims import Role from atlas_um.helpers.csv import dump_data_to_csv from atlas_um.helpers.fields import ( IntegerNoneField, KeyValueField, TimezoneField, ) from atlas_um.helpers.ordering import QueryOrdering from atlas_um.helpers.pagination import QueryPagination from atlas_um.settings import Settings from . import reports from .forms import DNAAccountForm, ImportAuth0UsersForm from .forms import product_form_factory from .services import ( CreateDNAAccountService, DisableDNAAccountClaimsService, UpdateDNAAccountClaimsService, ) from .services import UpdateDNAAccountService, SuspendDNAAccountService dna_accounts = flask.Blueprint("dna_accounts", __name__) @dna_accounts.route("", methods=["GET"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) @use_kwargs( { "search_term": fields.Str(load_default=""), "page": fields.Int(load_default=1), "per_page": fields.Int(load_default=Settings.ITEMS_PER_PAGE), "order": fields.Str(load_default=""), "status": fields.Str(load_default=""), "resource_group_id": IntegerNoneField(load_default=None), "tag_id": IntegerNoneField(load_default=None), "affected": KeyValueField(load_default=""), "token_size_violation": fields.Boolean(load_default=False), "expire_soon": fields.Boolean(load_default=False), "modal": fields.Boolean(load_default=False), }, location="query", ) def list( search_term, page, per_page, order, status, resource_group_id, tag_id, affected, token_size_violation, expire_soon, modal, ): if search_term: query = pgdb.DNAAccount.query.search(search_term) else: query = pgdb.DNAAccount.query.with_relations() if status: query = query.by_status(status) if resource_group_id: query = query.by_resource_group_id(resource_group_id) if tag_id: query = query.by_tag_id(tag_id) affecting_instance = None if affected: query, affecting_instance = query.affected_by(*affected) if token_size_violation: query = query.token_size_violation() if expire_soon: query = query.expire_soon() ordering = QueryOrdering( query, [ pgdb.DNAAccount.id, pgdb.DNAAccount.given_name, pgdb.DNAAccount.family_name, pgdb.DNAAccount.email, ], order, default_is_desc=True, ) query = ordering.query pagination = QueryPagination( page=page, search=bool(search_term), per_page=per_page, query=query ) if modal: template = "dna_accounts/list_dna_accounts_modal.html" else: template = "dna_accounts/list_dna_accounts.html" return flask.render_template( template, search_term=search_term, accounts=pagination.items, pagination=pagination, ordering=ordering, status=status, DNAAccountStatuses=pgdb.DNAAccountStatuses, resource_groups=pgdb.ResourceGroup.query.active().order_by( pgdb.ResourceGroup.name ), tags=pgdb.Tag.query.active(), resource_group_id=resource_group_id, tag_id=tag_id, affecting_instance=affecting_instance, token_size_violation=token_size_violation, expire_soon=expire_soon, ) @dna_accounts.route("/export", methods=["GET"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) @use_kwargs( { "search_term": fields.Str(load_default=""), "status": fields.Str(load_default=""), "resource_group_id": IntegerNoneField(load_default=None), "order": fields.Str(load_default=""), "tag_id": IntegerNoneField(load_default=None), "affected": KeyValueField(load_default=""), "token_size_violation": fields.Boolean(load_default=False), "expire_soon": fields.Boolean(load_default=False), "timezone": TimezoneField(load_default="utc"), }, location="query", ) def export( search_term, status, resource_group_id, tag_id, order, affected, token_size_violation, expire_soon, timezone, ): response = flask.make_response( dump_data_to_csv( reports.dna_accounts_and_assigned_claims( search_term, status, resource_group_id, tag_id, order, affected, token_size_violation, expire_soon, timezone=timezone, ) ) ) response.mimetype = "text/csv" response.headers["Content-Disposition"] = "attachment; filename=users.csv" return response @dna_accounts.route("/search", methods=["GET"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) @use_kwargs( { "search_term": fields.Str(load_default=""), }, location="query", ) def search(search_term): recent_accounts = flask.session.get("recent_account_searches") or [] if search_term and len(search_term) > 2: search_results = pgdb.DNAAccount.query.search(search_term) recent_searches = None else: search_results = None recent_searches = pgdb.DNAAccount.query.by_ids(recent_accounts) return flask.render_template( "dna_accounts/search.html", recent_searches=recent_searches, search_results=search_results, ) @dna_accounts.route("/create", methods=["GET", "POST"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def create(): form = DNAAccountForm() if form.validate_on_submit(): result = CreateDNAAccountService.execute(**form.data) account_id = result.value.id tasks.sync_dna_account_with_auth0.delay(account_id) flash("The user is successfully created.", "positive") return flask.redirect( flask.url_for("dna_accounts.products", account_id=account_id) ) return flask.render_template( "dna_accounts/create_dna_account.html", form=form ) @dna_accounts.route("/", methods=["GET"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def show(account_id): account = pgdb.DNAAccount.query.get_or_404(account_id) recent_accounts = flask.session.get("recent_account_searches") or [] if account.id not in recent_accounts: recent_accounts.insert(0, account.id) flask.session["recent_account_searches"] = recent_accounts[ : flask.current_app.config["RECENT_ACCOUNT_SEARCHES_QTY"] ] return flask.render_template( "dna_accounts/show_dna_account.html", account=account, DNAAccountStatuses=pgdb.DNAAccountStatuses, ) @dna_accounts.route("//edit", methods=["GET", "POST"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def edit(account_id): statuses = pgdb.DNAAccountStatuses account = pgdb.DNAAccount.query.get_or_404(account_id) form = DNAAccountForm(obj=account) if form.data.get("action") == form.Actions.SUSPEND.name: SuspendDNAAccountService.execute(account) return flask.redirect( flask.url_for("dna_accounts.show", account_id=account_id) ) elif form.validate_on_submit(): UpdateDNAAccountService.execute(account, **form.data) return flask.redirect( flask.url_for("dna_accounts.show", account_id=account_id) ) return flask.render_template( "dna_accounts/edit_dna_account.html", account=account, form=form, statuses=statuses, ) @dna_accounts.route( "//send_invitation", methods=["POST"], defaults={"resource_group_id": None}, ) @dna_accounts.route( "//send_invitation/", methods=["POST"], ) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def send_invitation(account_id, resource_group_id): account = pgdb.DNAAccount.query.get_or_404(account_id) tasks.send_dna_invitation.delay( account.id, current_user.dna_account.id, resource_group_id ) flash("Invitation sent.", "positive") return flask.redirect( flask.url_for("dna_accounts.show", account_id=account.id) ) @dna_accounts.route("//products", methods=["GET"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def products(account_id): resource_group = ( pgdb.ResourceGroup.query.active().order_by("name").first_or_404() ) return flask.redirect( flask.url_for( ".show_product", account_id=account_id, product_code=resource_group.id, ) ) @dna_accounts.route( "//products/", methods=["GET"] ) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def show_product(account_id, product_code): account = pgdb.DNAAccount.query.get_or_404(account_id) resource_group = ( pgdb.ResourceGroup.query.active().by_id(product_code).first_or_404() ) product = pgdb.Product(resource_group, account) return flask.render_template( "dna_accounts/show_product.html", account=account, product=product, DNAAccountStatuses=pgdb.DNAAccountStatuses, ) @dna_accounts.route( "//products//edit", methods=["GET", "POST"], ) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def edit_product(account_id, product_code): account = pgdb.DNAAccount.query.get_or_404(account_id) resource_group = ( pgdb.ResourceGroup.query.active().by_id(product_code).first_or_404() ) product = pgdb.Product(resource_group, account) form = product_form_factory(product) if form.validate_on_submit(): if form.data.get("action") == form.Actions.DISABLE.name: DisableDNAAccountClaimsService.execute( account, account.claim_values, resource_group ) flash(f"Disabled {resource_group.name} successfully", "positive") else: UpdateDNAAccountClaimsService.execute( account, form.claim_values, resource_group ) if account.account_violates_limit: flash( "The resulting token size is too big! " "Please, consider to use less claims.", "negative", ) return flask.redirect( flask.url_for( "dna_accounts.show_product", account_id=account_id, product_code=product_code, ) ) return flask.render_template( "dna_accounts/edit_product.html", account=account, product=product, form=form, ) @dna_accounts.route("/import", methods=["GET", "POST"]) @claims_required([Role(Role.Values.admin), Role(Role.Values.manager)]) def import_auth0_users(): form = ImportAuth0UsersForm() if form.validate_on_submit(): tasks.import_auth0_users_by_product.delay( form.data.get("product"), form.data.get("user_ids"), form.data.get("batch_from"), form.data.get("batch_to"), ) flash("Pending 'Import Auth0 users' task...", "positive") return flask.redirect(flask.url_for("dna_accounts.list")) return flask.render_template( "dna_accounts/import_auth0_users.html", form=form )