Skip to content

Commit 1a6efce

Browse files
committed
Implement SSO attributes de-duplication for email and names
1 parent a0501f3 commit 1a6efce

File tree

3 files changed

+44
-4
lines changed

3 files changed

+44
-4
lines changed

api/institutions/authentication.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from api.waffle.utils import flag_is_active
1717

1818
from framework import sentry
19-
from framework.auth import get_or_create_institutional_user
19+
from framework.auth import get_or_create_institutional_user, deduplicate_sso_attributes
20+
from framework.auth.exceptions import MultipleSSOEmailError
2021

2122
from osf import features
2223
from osf.exceptions import InstitutionAffiliationStateError
@@ -223,8 +224,13 @@ def authenticate(self, request):
223224
f'sso_email={sso_email}, sso_identity={sso_identity}]',
224225
)
225226

227+
# Deduplicate full name first if it is provided
228+
if fullname:
229+
fullname = deduplicate_sso_attributes(institution, sso_identity, 'fullname', fullname)
226230
# Use given name and family name to build full name if it is not provided
227231
if given_name and family_name and not fullname:
232+
given_name = deduplicate_sso_attributes(institution, sso_identity, 'given_name', given_name)
233+
family_name = deduplicate_sso_attributes(institution, sso_identity, 'family_name', family_name)
228234
fullname = given_name + ' ' + family_name
229235

230236
# Non-empty full name is required. Fail the auth and inform sentry if not provided.
@@ -235,6 +241,21 @@ def authenticate(self, request):
235241
sentry.log_message(message)
236242
raise PermissionDenied(detail='InstitutionSsoMissingUserNames')
237243

244+
# Deduplicate sso email, currently we only handle duplicate sso email instead of multiple sso email
245+
try:
246+
sso_email = deduplicate_sso_attributes(
247+
institution,
248+
sso_identity,
249+
'sso_email',
250+
sso_email,
251+
ignore_errors=False,
252+
)
253+
except MultipleSSOEmailError:
254+
message = f'Institution SSO Error: multiple SSO email [sso_email={sso_email}, sso_identity={sso_identity}, institution={institution._id}]'
255+
sentry.log_message(message)
256+
logger.error(message)
257+
# TODO: this requires a CAS hotfix to handle `detail='InstitutionMultipleSSOEmails'`
258+
raise PermissionDenied(detail='InstitutionMultipleSSOEmails')
238259
# Attempt to find an existing user that matches the email(s) provided via SSO. Create a new one if not found.
239260
# If a user is found, it is possible that the user is inactive (e.g. unclaimed, disabled, unconfirmed, etc.).
240261
# If a new user is created, the user object is confirmed but not registered (i.e. inactive until registered).

framework/auth/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
from django.utils import timezone
44

5-
from framework import bcrypt
5+
from framework import bcrypt, sentry
66
from framework.auth import signals
77
from framework.auth.core import Auth
88
from framework.auth.core import get_user, generate_verification_key
9-
from framework.auth.exceptions import DuplicateEmailError
9+
from framework.auth.exceptions import DuplicateEmailError, MultipleSSOEmailError
1010
from framework.auth.tasks import update_user_from_activity
1111
from framework.auth.utils import LogLevel, print_cas_log
1212
from framework.celery_tasks.handlers import enqueue_task
@@ -154,11 +154,26 @@ def get_or_create_institutional_user(fullname, sso_email, sso_identity, primary_
154154
# CASE 5/5: If no user is found, create a confirmed user and return the user and sso identity.
155155
# Note: Institution users are created as confirmed with a strong and random password. Users don't need the
156156
# password since they sign in via SSO. They can reset their password to enable email/password login.
157-
user = OSFUser.create_confirmed(sso_email, str(uuid.uuid4()), fullname)
157+
# user = OSFUser.create_confirmed(sso_email, str(uuid.uuid4()), fullname)
158+
user = OSFUser.create_confirmed(sso_email, 'abCD12#$', fullname)
158159
user.add_system_tag(institution_source_tag(primary_institution._id))
159160
return user, True, None, None, sso_identity
160161

161162

163+
def deduplicate_sso_attributes(institution, sso_identity, attr_name, attr_value, delimiter=';', ignore_errors=True):
164+
if delimiter not in attr_value:
165+
return attr_value
166+
value_set = set(attr_value.split(delimiter))
167+
if len(value_set) != 1:
168+
message = (f'Multiple values {attr_value} found for SSO attribute {attr_name}: '
169+
f'[institution_id={institution._id}, sso_identity={sso_identity}]')
170+
if ignore_errors:
171+
sentry.log_message(message)
172+
return attr_value
173+
raise MultipleSSOEmailError(message)
174+
return value_set.pop()
175+
176+
162177
def get_or_create_user(fullname, address, reset_password=True, is_spam=False):
163178
"""
164179
Get or create user by fullname and email address.

framework/auth/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ class MergeConflictError(EmailConfirmTokenError):
6565
"""Raised if a merge is not possible due to a conflict"""
6666
message_short = language.CANNOT_MERGE_ACCOUNTS_SHORT
6767
message_long = language.CANNOT_MERGE_ACCOUNTS_LONG
68+
69+
70+
class MultipleSSOEmailError(AuthError):
71+
pass

0 commit comments

Comments
 (0)