Skip to content

Commit

Permalink
Merge branch 'release/v21.11.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-c committed Nov 10, 2021
2 parents 6d70471 + de7e6c3 commit d9ab5f3
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 35 deletions.
3 changes: 2 additions & 1 deletion portal/config/eproms/Organization.json
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,8 @@
"research_protocols": [
{"name": "IRONMAN v2", "retired_as_of": "2018-05-29T04:00:00Z"},
{"name": "IRONMAN v3", "retired_as_of": "2020-10-19T04:00:00Z"},
{"name": "IRONMAN v5"}
{"name": "IRONMAN v5"},
{"name": "EMPRO v1"}
],
"url": "http://us.truenth.org/identity-codes/research-protocol"
}
Expand Down
38 changes: 22 additions & 16 deletions portal/models/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ def send_2fa_email(user):
db.session.commit()


def user_requires_2fa(user):
"""Logic to determine if user requires 2FA at this time"""
assert user
return (
current_app.config.get("ENABLE_2FA") and
not current_app.testing and
not getattr(getattr(request, 'oauth', None), 'user', None) and
not user.has_role(ROLE.WRITE_ONLY.value) and
user.has_role(
ROLE.ADMIN.value,
ROLE.ANALYST.value,
ROLE.CLINICIAN.value,
ROLE.PRIMARY_INVESTIGATOR.value,
ROLE.CLINICIAN.value,
ROLE.RESEARCHER.value,
ROLE.STAFF.value,
ROLE.STAFF_ADMIN.value,
) and
session.get('2FA_verified') != '2FA verified')


def login_user(user, auth_method=None):
"""Common entry point for all login flows - direct here for bookkeeping
Expand All @@ -46,22 +67,7 @@ def login_user(user, auth_method=None):
"""
# beyond patients and care givers, 2FA is required. confirm or initiate
if (
current_app.config.get("ENABLE_2FA") and
not current_app.testing and
not getattr(getattr(request, 'oauth', None), 'user', None) and
not user.has_role(ROLE.WRITE_ONLY.value) and
user.has_role(
ROLE.ADMIN.value,
ROLE.ANALYST.value,
ROLE.CLINICIAN.value,
ROLE.PRIMARY_INVESTIGATOR.value,
ROLE.CLINICIAN.value,
ROLE.RESEARCHER.value,
ROLE.STAFF.value,
ROLE.STAFF_ADMIN.value,
) and
session.get('2FA_verified') != '2FA verified'):
if user_requires_2fa(user):
# log user back out, in case a flow already promoted them
flask_user_logout()

Expand Down
56 changes: 45 additions & 11 deletions portal/static/js/src/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2759,9 +2759,6 @@ export default (function() {
},
updateRolesData: function(event) {
var visibleRoles = $("#rolesGroup input:checkbox:checked:visible");
var roles = $("#rolesGroup input:checkbox:checked").map(function() {
return {name: $(this).val()};
}).get();
/*
* check if a role is selected
*/
Expand All @@ -2771,17 +2768,51 @@ export default (function() {
$(".put-roles-error").html("A role must be selected.");
return false;
}
var isChecked = $(event.target).is(":checked");
var changedRole = event.target.value;
var roles = [];
var self = this;
$(".put-roles-error").html("");
this.modules.tnthAjax.putRoles(this.subjectId, {"roles": roles}, $(event.target));
/*
* refresh user roles list since it has been uploaded
*/
this.initUserRoles({clearCache: true});
$("#rolesGroup").addClass("loading");
this.modules.tnthAjax.getRoles(this.subjectId, function(data) {
var dataRoles = [];
if (data.roles) {
dataRoles = data.roles.map(function(role) {
return role.name;
});
if (!isChecked) {
//removed from existing role list
dataRoles = dataRoles.filter(function(role){
return role !== changedRole;
});
} else {
//combine checked role with existing roles
dataRoles = [...dataRoles, changedRole];
//remove duplicate role(s)
dataRoles = dataRoles.filter(function(value, index) {
return dataRoles.indexOf(value) === index;
});
}
}
roles = dataRoles.map(function(role) {
return {
"name": role
}
});
self.modules.tnthAjax.putRoles(self.subjectId, {"roles": roles}, $(event.target), function() {
$("#rolesGroup").removeClass("loading");
/*
* refresh user roles list since it has been uploaded
*/
self.initUserRoles({clearCache: true});
});
}, {"clearCache": true});
},
updateRolesUI: function(roles) {
if (!roles) return;
roles.forEach(role => {
$("#rolesGroup input[value='"+role+"']").attr("checked", true);
$("#rolesGroup input[type='checkbox']").each(function() {
if (roles.indexOf($(this).val()) !== -1) $(this).attr("checked", true);
else $(this).attr("checked", false);
});
},
isAdminOnlyEditableRole: function(role) {
Expand Down Expand Up @@ -3175,7 +3206,10 @@ export default (function() {
}
//adding a test substudy consent should only be allowed in Test environment
if (!this.isTestEnvironment()) {
return false;
//allowed in non-Test environment based on additional check, e.g. user role, patient role, config etc.
if (!this.isConsentEditable()) {
return false;
}
}
//should only show add substudy consent row if the subject is a patient and the user is a staff/staff admin
return this.hasCurrentConsent() && !this.hasSubStudyConsent() && this.isSubjectPatient() && this.isStaff();
Expand Down
18 changes: 18 additions & 0 deletions portal/static/less/eproms.less
Original file line number Diff line number Diff line change
Expand Up @@ -4773,6 +4773,24 @@ input.clinic {
margin-bottom: 4px;
}
}
#rolesGroup {
position: relative;
&:before {
z-index: -1;
}
&.loading {
&:before {
position: absolute;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
background:transparent;
z-index: 5;
}
}
}
.create-account-container {
position: relative;
padding: 2em;
Expand Down
11 changes: 7 additions & 4 deletions portal/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
MockFlaskDanceProvider,
)
from ..models.intervention import Intervention, UserIntervention
from ..models.login import login_user, send_2fa_email
from ..models.login import login_user, send_2fa_email, user_requires_2fa
from ..models.role import ROLE
from ..models.user import (
User,
Expand Down Expand Up @@ -580,9 +580,12 @@ def next_after_login():
invited_user.promote_to_registered(user)
db.session.commit()

# on this brand new promoted account, must fake/suppress 2fa or we'll
# repeat the cycle as part of the `login_user()` call
session['2FA_verified'] = '2FA verified'
# on this brand new promoted account, auto-login users whom do not require 2fa
# otherwise, redirect for fresh login to enforce 2fa validation
if user_requires_2fa(invited_user):
flash(_("Your password has been set. Please log in to proceed."), "info")
return redirect('/')

login_user(invited_user, 'password_authenticated')

if preserve_next_across_sessions:
Expand Down
7 changes: 6 additions & 1 deletion portal/views/patch_flask_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ def patch_forgot_password():
email = form.email.data
user, user_email = user_manager.find_user_by_email(email)

if user:
# Do NOT allow non registered to change password
non_registered_roles = set(current_app.config['PRE_REGISTERED_ROLES'])
current_roles = {r.name for r in user.roles}
disjoint = current_roles.isdisjoint(non_registered_roles)

if disjoint and user:
with force_locale(user.locale_code):
user_manager.send_reset_password_email(email)

Expand Down
5 changes: 3 additions & 2 deletions portal/views/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,14 +559,15 @@ def confirm_identity():
'confirm_identity.html', user=current_user(),
redirect_url=request.args.get("redirect_url", "/"))


@portal.route('/initial-queries', methods=['GET', 'POST'])
def initial_queries():
"""Initial consent terms, initial queries view function"""
user = get_user(current_user().id, 'edit')
if not user:
if not current_user():
# Shouldn't happen, unless user came in on a bookmark
current_app.logger.debug("initial_queries (no user!) -> landing")
return redirect('/')
user = get_user(current_user().id, 'edit')
if user.deleted:
abort(400, "deleted user - operation not permitted")
if request.method == 'POST':
Expand Down

0 comments on commit d9ab5f3

Please sign in to comment.