Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/institutional signers #727

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions app/manage/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from projects.models import TeamComment
from projects.serializers import HostedFileSerializer, HostedFileDownloadSerializer
from projects.models import AGREEMENT_FORM_TYPE_MODEL, AGREEMENT_FORM_TYPE_FILE
from projects.models import InstitutionalOfficial

# Get an instance of a logger
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1157,6 +1158,54 @@ def grant_view_permission(request, project_key, user_email):
participant = project.participant_set.get(user__email=user_email)
participant.permission = 'VIEW'
participant.save()

# Check if this project allows institutional signers
if project.institutional_signers:

# Check if this is a signing official
try:
official = InstitutionalOfficial.objects.get(
project=project,
user=participant.user,
)

# Iterate linked members
for member_email in official.member_emails:

logger.debug(f"Institutional signer/{participant.user.email}: Checking for existing linked member '{member_email}'")

# Check if a participant exists for this email with no VIEW permission
if Participant.objects.filter(project=project, user__email=member_email).exclude(permission="VIEW").exists():

# Fetch them
member_participant = Participant.objects.get(project=project, user__email=member_email)

# Approve signed agreement forms
for signed_agreement_form in SignedAgreementForm.objects.filter(project=project, user=member_participant.user):

# If allows institutional signers, auto-approve
if signed_agreement_form.agreement_form.institutional_signers:

signed_agreement_form.status = "A"
signed_agreement_form.save()

# Grant this user access immediately if all agreement forms are accepted
for agreement_form in project.agreement_forms.all():
if not SignedAgreementForm.objects.filter(
agreement_form=agreement_form,
project=project,
user=member_participant.user,
status="A"
):
break
else:

# Call this method to process the access
grant_view_permission(request, project_key, member_email)

except ObjectDoesNotExist:
pass

except Exception as e:
logger.exception(
'[HYPATIO][DEBUG][grant_view_permission] User {user} could not have permission added to project {project_key}: {e}'.format(
Expand Down Expand Up @@ -1226,6 +1275,26 @@ def remove_view_permission(request, project_key, user_email):
participant = project.participant_set.get(user__email=user_email)
participant.permission = None
participant.save()

# Check if this project allows institutional signers
if project.institutional_signers:

# Check if this is a signing official
try:
official = InstitutionalOfficial.objects.get(
project=project,
user=participant.user,
)

# Iterate linked members
for member_email in official.member_emails:

# Remove their access
remove_view_permission(request, project_key, member_email)

except ObjectDoesNotExist:
pass

except Exception as e:
logger.exception(
'[HYPATIO][ERROR][grant_view_permission] User {user} could not have permission remove from project {project_key}: {e}'.format(
Expand Down
32 changes: 32 additions & 0 deletions app/projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import re
import importlib
from datetime import datetime
from typing import Optional, Tuple

import boto3
from botocore.exceptions import ClientError
Expand Down Expand Up @@ -489,6 +490,37 @@ class Meta:
verbose_name_plural = 'Signed Agreement Forms'


def get_institutional_signer_details(self) -> Optional[Tuple[str, str, list[str]]]:
"""
Checks the SignedAgreementForm to see if it has been signed by an institutional official.
If so, it returns the name of the institution and email of the institutional official along with a list of
emails for the members they are representing.

:returns: A tuple containing the institution name, official email, and a list of member emails if this is an
institutional signer; otherwise tuple of None, None, None
:rtype: Optional[Tuple[str, str, list[str]]], defaults to Tuple[None, None, None]
"""
if self.agreement_form.institutional_signers:

# Fields should contain whether this is an institutional official or not
if self.fields and self.fields.get("registrant_is", "").lower() == "official":

# Ensure member emails is a list
member_emails = self.fields.get("member_emails", [])
if isinstance(member_emails, str):
member_emails = [member_emails]
elif not isinstance(member_emails, list):
raise ValidationError(f"Unhandled state of 'member_emails' field: {type(member_emails)}/{member_emails}")

# Cleanup emails to remove whitespace, if any
member_emails = [email.strip() for email in member_emails]

# Return values
return self.fields["institute_name"], self.user.email, member_emails

return None, None, None


class DataUseReportRequest(models.Model):
"""
This model describes a request for a participant to report on data use.
Expand Down
32 changes: 19 additions & 13 deletions app/projects/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,22 @@ def signed_agreement_form_pre_save_handler(sender, **kwargs):
logger.debug(f"Pre-save: {instance}")

# Check for specific types of forms that require additional handling
if instance.status == "A" and instance.agreement_form.short_name == "4ce-dua" \
and instance.fields.get("registrant-is") == "official":
logger.debug(f"Pre-save institutional official: {instance}")

# Create official and member objects
official = InstitutionalOfficial.objects.create(
user=instance.user,
institution=instance.fields["institute-name"],
project=instance.project,
signed_agreement_form=instance,
member_emails=instance.fields["member-emails"],
)
official.save()
institute_name, official_email, member_emails = instance.get_institutional_signer_details()
if instance.status == "A" and institute_name and official_email and member_emails:
logger.debug(f"Pre-save institutional official AgreementForm: {instance}")

# Ensure they don't already exist
if InstitutionalOfficial.objects.filter(user=instance.user, project=instance.project).exists():
logger.debug(f"InstitutionalOfficial already exists for {instance.user}/{instance.project}")

else:

# Create official and member objects
official = InstitutionalOfficial.objects.create(
user=instance.user,
institution=institute_name,
project=instance.project,
signed_agreement_form=instance,
member_emails=member_emails,
)
official.save()
Loading