Skip to content

Commit

Permalink
Merge branch 'biocore:master' into project_specific_home_page
Browse files Browse the repository at this point in the history
  • Loading branch information
ayobi authored Sep 11, 2024
2 parents 922c93f + 8198e5f commit 6376e42
Show file tree
Hide file tree
Showing 14 changed files with 2,485 additions and 1,470 deletions.
2 changes: 1 addition & 1 deletion ci/conda_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
flake8
flask
flask < 3.0.0
natsort
pycryptodome
redis-py
Expand Down
108 changes: 103 additions & 5 deletions microsetta_interface/implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1144,12 +1144,20 @@ def get_account(*, account_id=None):
@prerequisite([ACCT_PREREQS_MET])
def get_account_details(*, account_id=None):
has_error, account, _ = ApiRequest.get('/accounts/%s' % account_id)

if has_error:
return account

has_error, stats, _ = ApiRequest.get(f'/accounts/{account_id}/'
'removal_queue')

if has_error:
return stats

return _render_with_defaults('account_details.jinja2',
CREATE_ACCT=False,
account=account)
account=account,
requested_deletion=stats['status'])


@prerequisite([ACCT_PREREQS_MET])
Expand Down Expand Up @@ -1316,6 +1324,29 @@ def get_create_nonhuman_source(*, account_id=None):
account_id=account_id)


# Note: ideally this would be represented as a DELETE, not as a POST
# However, it is used as a form submission action, and HTML forms do not
# support delete as an action
@prerequisite([ACCT_PREREQS_MET])
def post_request_account_removal(*, account_id, body):
# PUT is used to add the account_id to the queue
# DELETE is used to remove the account_id from the queue, if it's
# still there.

user_delete_reason = body.get('user_delete_reason')

url = f'/accounts/{account_id}/removal_queue' \
f'?user_delete_reason={user_delete_reason}'

has_error, put_output, _ = ApiRequest.put(url)

if has_error:
return put_output

return _render_with_defaults('request_account_deletion_confirm.jinja2',
account_id=account_id)


@prerequisite([ACCT_PREREQS_MET])
def post_create_nonhuman_source(*, account_id=None, body=None):
has_error, sources_output, _ = ApiRequest.post(
Expand Down Expand Up @@ -2782,8 +2813,21 @@ def get_interactive_account_search(email_query):
if do_return:
return email_diagnostics

accounts = [{"email": acct['email'], "account_id": acct['id']}
for acct in email_diagnostics['accounts']]
accounts = []
for acct in email_diagnostics['accounts']:
if acct['auth_issuer'] is None and acct['auth_sub'] is None:
authrocket_status = "Missing"
elif acct['auth_issuer'] is None or acct['auth_sub'] is None:
authrocket_status = "Faulty - Contact Admin"
else:
authrocket_status = "Authenticated"
acct_diag = {
"email": acct['email'],
"account_id": acct['id'],
"authrocket_status": authrocket_status
}
accounts.append(acct_diag)

return _render_with_defaults('admin_home.jinja2',
accounts=accounts)

Expand All @@ -2797,6 +2841,7 @@ def post_account_delete(body):
raise Unauthorized()

account_to_delete = body.get('account_id')
delete_reason = body.get('delete_reason')
if account_to_delete is None:
raise Unauthorized()

Expand All @@ -2811,15 +2856,48 @@ def post_account_delete(body):
if accts_output['account_type'] != 'standard':
return get_rootpath()

has_error, delete_output, _ = ApiRequest.delete(
'/accounts/%s' % (account_to_delete,))
url = f'/admin/account_removal/{account_to_delete}' \
f'?delete_reason={delete_reason}'

has_error, delete_output, _ = ApiRequest.delete(url)

if has_error:
return delete_output

return get_rootpath()


def post_account_ignore_delete(body):
if not session.get(ADMIN_MODE_KEY, False):
raise Unauthorized()

account_details = session.get(LOGIN_INFO_KEY)
if account_details is None:
raise Unauthorized()

account_to_ignore = body.get('account_id')
if account_to_ignore is None:
raise Unauthorized()

# preserve 'standard-accounts-only' logic for now.
# admin accounts shouldn't be requesting their own deletion.
do_return, accts_output, _ = ApiRequest.get(
'/accounts/%s' % (account_to_ignore, ))
if do_return:
return accts_output

if accts_output['account_type'] != 'standard':
return get_rootpath()

url = '/admin/account_removal/%s' % account_to_ignore
has_error, ignore_output, _ = ApiRequest.put(url)

if has_error:
return ignore_output

return get_rootpath()


def get_perk_fulfillment_state():
if not session.get(ADMIN_MODE_KEY, False):
raise Unauthorized()
Expand Down Expand Up @@ -3381,6 +3459,26 @@ def post_campaign_edit(body):
return get_campaign_edit(campaign_info['campaign_id'])


def get_account_removal_requests():
if not session.get(ADMIN_MODE_KEY, False):
raise Unauthorized()

account_details = session.get(LOGIN_INFO_KEY)
if account_details is None:
raise Unauthorized()

do_return, diagnostics, _ = ApiRequest.get(
"/admin/account_removal/list",
params={}
)

if do_return:
return diagnostics

return _render_with_defaults('admin_requests_account_removal_list.jinja2',
diagnostics=diagnostics)


def get_submit_interest(campaign_id=None, source=None):
valid_campaign = False
campaign_info = None
Expand Down
5 changes: 3 additions & 2 deletions microsetta_interface/model_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
LANGUAGES = {
EN_US_KEY: Lang("en_US", "English"),
ES_MX_KEY: Lang("es_MX", "Español (México)"),
ES_ES_KEY: Lang("es_ES", "Español (España)"),
JA_JP_KEY: Lang("ja_JP", "日本語")
ES_ES_KEY: Lang("es_ES", "Español (España)")
# In case we want to reactivate Japanese in the future
# JA_JP_KEY: Lang("ja_JP", "日本語")
}

# We need a default full locale when a user's browser only sends a partial
Expand Down
64 changes: 64 additions & 0 deletions microsetta_interface/routes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,32 @@ paths:
schema:
type: string

# same as above
'/accounts/{account_id}/request/remove':
post:
operationId: microsetta_interface.implementation.post_request_account_removal
tags:
- Account
parameters:
- $ref: '#/components/parameters/account_id'
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
user_delete_reason:
type: string
nullable: true
responses:
'200':
description: Remove account request submitted
content:
text/html:
schema:
type: string


'/accounts/{account_id}/sources/{source_id}/claim_samples':
post:
operationId: microsetta_interface.implementation.post_claim_samples
Expand Down Expand Up @@ -1064,6 +1090,9 @@ paths:
account_id:
type: string
nullable: false
delete_reason:
type: string
nullable: true

responses:
'200':
Expand All @@ -1073,6 +1102,28 @@ paths:
schema:
type: string

'/admin/account_ignore_delete':
post:
operationId: microsetta_interface.implementation.post_account_ignore_delete
tags:
- Admin
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
account_id:
type: string
nullable: false
responses:
'200':
description: Account successfully removed from delete queue, redirect to home
content:
text/html:
schema:
type: string

'/admin/perk_fulfillment_state':
get:
operationId: microsetta_interface.implementation.get_perk_fulfillment_state
Expand Down Expand Up @@ -1439,6 +1490,19 @@ paths:
schema:
type: string

'/admin/account_removal/list':
get:
operationId: microsetta_interface.implementation.get_account_removal_requests
tags:
- Admin
responses:
'200':
description: List of account removal requests for admin users to view/edit
content:
text/html:
schema:
type: string


components:
parameters:
Expand Down
51 changes: 50 additions & 1 deletion microsetta_interface/templates/account_details.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@
}
},cc);
});
function verifyDeleteUserRequest(){
let confirmMsg = "{{ _('You are requesting to delete your account.') }} " +
"{{ _('This operation cannot be undone. Are you sure you want to delete this account?') }} ";
let reason = prompt("{{ _('Please provide a reason for deletion (Optional):') }}");
document.getElementById("user_delete_reason").value = reason;
return window.confirm(confirmMsg);
}
</script>
{% endblock %}
{% block breadcrumb %}
Expand Down Expand Up @@ -411,6 +422,7 @@
{% endif %}
</div>
</form>
<br><br>
{% if admin_mode %}
<br/>
<form action="/admin/account_delete" method="post">
Expand Down Expand Up @@ -438,8 +450,45 @@
<button type=submit" class="btn btn-danger">{{ _('Delete Account') }}</button>
</div>
</div>
</form>
{% endif %}
{% if not admin_mode and not CREATE_ACCT %}
{% if requested_deletion %}
<div class="container">
<div class="row">
<div class="col-12 {% if samples|length > 0 %}tooltipper{% endif %}" data-title="{{ _('Request account deletion.') }}">
<p>
</p>
<center>
{{ _('Your account removal request is being reviewed. You will be notified via email once your account has been deleted.') }}
</center>
</div>
</div>
</div>
{% else %}
<div class="container">
<div class="row">
<div class="col-12 {% if samples|length > 0 %}tooltipper{% endif %}" data-title="{{ _('Request account deletion.') }}">
<p>
</p>
<center>
<form name="request_delete_{{account.account_id}}_form"
method="post" action="/accounts/{{ account.account_id }}/request/remove"
onsubmit="return verifyDeleteUserRequest();">
{{ _('If you wish to delete this account, please click the following button to submit your request to an administrator.') }}
<input type="hidden" id="user_delete_reason" name="user_delete_reason" value="{{ account.user_delete_reason}}">
<br><br>
<button type="submit" class="btn btn-outline-danger">{{ _('Request Account Deletion') }}</button>
<br><br>
{{ _('IMPORTANT: Once you click this button, the request cannot be undone. Your account cannot be restored after it has been deleted.') }}
</form>
</center>
</div>
</div>
</div>
{% endif %}
{% endif %}
</form>
</div>
</div>
{% endblock %}
10 changes: 8 additions & 2 deletions microsetta_interface/templates/admin_home.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,24 @@
<div class="col-sm">
<i>{{ _('Account ID') }}</i>
</div>
<div class="col-sm">
<i>{{ _('AuthRocket Status') }}</i>
</div>
</div>
{% for account in accounts %}
<div class="container list-group-item {{loop.cycle('odd', 'even') }}">
<div class="row">
<div class="col-sm">
<div class="col-sm" style="word-wrap: break-word; overflow: hidden;">
<a href="/accounts/{{account.account_id |e}}">
{{ account.email |e}}
</a>
</div>
<div class="col-sm">
<div class="col-sm" style="word-wrap: break-word; overflow: hidden;">
{{ account.account_id|e }}
</div>
<div class="col-sm">
{{ account.authrocket_status|e }}
</div>
</div>
</div>
{% endfor %}
Expand Down
Loading

0 comments on commit 6376e42

Please sign in to comment.