Skip to content

Commit 75ffbb0

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into feature/django_upgrade
2 parents 721ef90 + c6fda7f commit 75ffbb0

18 files changed

+572
-139
lines changed

addons/bitbucket/api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from addons.bitbucket import settings
44

55
from framework.exceptions import HTTPError
6-
6+
from osf.utils.fields import ensure_str
77
from website.util.client import BaseClient
88

99

1010
class BitbucketClient(BaseClient):
1111

1212
def __init__(self, access_token=None):
13-
self.access_token = access_token
13+
self.access_token = ensure_str(access_token)
1414

1515
@property
1616
def _default_headers(self):

api/institutions/authentication.py

Lines changed: 53 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,38 @@
1717

1818
from osf import features
1919
from osf.models import Institution
20+
from osf.models.institution import SharedSsoAffiliationFilterCriteriaAction
2021

2122
from website.mails import send_mail, WELCOME_OSF4I
2223
from website.settings import OSF_SUPPORT_EMAIL, DOMAIN
2324

2425
logger = logging.getLogger(__name__)
2526

26-
# This map defines how to find the secondary institution IdP which uses the shared SSO of a primary
27-
# IdP. Each map entry has the following format.
27+
# This map defines how to find the secondary institution which uses SSO of a primary one. Each map
28+
# entry has the following format.
2829
#
2930
# '<ID of the primary institution A>': {
30-
# 'criteria': 'attribute',
31-
# 'attribute': '<the attribute name for identifying secondary institutions>',
32-
# 'institutions': {
33-
# '<attribute value for identifying institution A1>': '<ID of secondary institution A1>',
34-
# '<attribute value for identifying institution A2>': '<ID of secondary institution A2>',
35-
# ...
36-
# },
37-
# ...
38-
# }
39-
#
40-
# Currently, the only active criteria is "attribute", which the primary institution IdP releases to
41-
# OSF for us to identify the secondary institution. Another option is "emailDomain". For example:
42-
#
43-
# '<ID of the primary institution B>': {
44-
# 'criteria': 'emailDomain',
45-
# 'institutions': {
46-
# '<the email domain for identifying institution B1>': '<ID of secondary institution B1',
47-
# '<the email domain for identifying institution B2>': '<ID of secondary institution B2',
48-
# ...
49-
# }
50-
# ...
31+
# 'attribute_name': '<the attribute name for identifying secondary institutions>',
32+
# 'criteria_action': '<the action to perform between the attribute value and criteria value',
33+
# 'criteria_value': '<the value that>
34+
# 'institution_id': 'the ID of the secondary institution',
5135
# }
36+
# For now, this map is temporarily defined here but will be moved to settings or be re-implemented
37+
# in model via relationships later. In addition, we should be able to make the attribute name fixed
38+
# since CAS can normalize them into "sharedSsoFilter" ahead of time.
5239
#
5340
INSTITUTION_SHARED_SSO_MAP = {
5441
'brown': {
55-
'criteria': 'attribute',
56-
'attribute': 'isMemberOf',
57-
'institutions': {
58-
'thepolicylab': 'thepolicylab',
59-
},
42+
'attribute_name': 'isMemberOf',
43+
'criteria_action': SharedSsoAffiliationFilterCriteriaAction.EQUALS_TO.value,
44+
'criteria_value': 'thepolicylab',
45+
'institution_id': 'thepolicylab',
46+
},
47+
'fsu': {
48+
'attribute_name': 'userRoles',
49+
'criteria_action': SharedSsoAffiliationFilterCriteriaAction.CONTAINS.value,
50+
'criteria_value': 'FSU_OSF_MAGLAB',
51+
'institution_id': 'nationalmaglab',
6052
},
6153
}
6254

@@ -158,34 +150,42 @@ def authenticate(self, request):
158150
secondary_institution = None
159151
if provider['id'] in INSTITUTION_SHARED_SSO_MAP:
160152
switch_map = INSTITUTION_SHARED_SSO_MAP[provider['id']]
161-
criteria_type = switch_map.get('criteria')
162-
if criteria_type == 'attribute':
163-
attribute_name = switch_map.get('attribute')
164-
attribute_value = provider['user'].get(attribute_name)
165-
if attribute_value:
166-
secondary_institution_id = switch_map.get(
167-
'institutions',
168-
{},
169-
).get(attribute_value)
170-
logger.info('Institution SSO: primary=[{}], secondary=[{}], '
171-
'username=[{}]'.format(provider['id'], secondary_institution_id, username))
172-
secondary_institution = Institution.load(secondary_institution_id)
173-
if not secondary_institution:
174-
# Log errors and inform Sentry but do not raise an exception if OSF fails
175-
# to load the secondary institution from database
176-
message = 'Institution SSO Error: invalid secondary institution [{}]; ' \
177-
'primary=[{}], username=[{}]'.format(attribute_value, provider['id'], username)
178-
logger.error(message)
179-
sentry.log_message(message)
180-
else:
181-
# SSO from primary institution only
182-
logger.info('Institution SSO: primary=[{}], secondary=[None], '
183-
'username=[{}]'.format(provider['id'], username))
153+
attribute_name = switch_map.get('attribute_name')
154+
criteria_action = switch_map.get('criteria_action')
155+
criteria_value = switch_map.get('criteria_value')
156+
attribute_value = provider['user'].get(attribute_name)
157+
# Check affiliation filter criteria and retrieve the secondary institution ID
158+
secondary_institution_id = None
159+
if criteria_action == SharedSsoAffiliationFilterCriteriaAction.EQUALS_TO.value:
160+
secondary_institution_id = switch_map.get('institution_id') if criteria_value == attribute_value else None
161+
elif criteria_action == SharedSsoAffiliationFilterCriteriaAction.CONTAINS.value:
162+
secondary_institution_id = switch_map.get('institution_id') if criteria_value in attribute_value else None
184163
else:
185-
message = 'Institution SSO Error: invalid criteria [{}]; ' \
186-
'primary=[{}], username=[{}]'.format(criteria_type, provider['id'], username)
164+
message = 'Institution Shared SSO Error: invalid affiliation filter criteria action [{}]; ' \
165+
'primary=[{}], username=[{}]'.format(criteria_action, provider['id'], username)
187166
logger.error(message)
188167
sentry.log_message(message)
168+
# Attempt to load the secondary institution by ID
169+
if secondary_institution_id:
170+
logger.info(
171+
'Institution Shared SSO Eligible: primary=[{}], secondary=[{}], '
172+
'filter=[{}: {} {} {}], username=[{}]'.format(
173+
provider['id'], secondary_institution_id, attribute_name,
174+
attribute_value, criteria_action, criteria_value, username,
175+
),
176+
)
177+
secondary_institution = Institution.load(secondary_institution_id)
178+
if not secondary_institution:
179+
# Log errors and inform Sentry but do not raise an exception if OSF fails
180+
# to load the secondary institution from database
181+
message = 'Institution Shared SSO Warning: invalid secondary institution [{}], primary=[{}], ' \
182+
'username=[{}]'.format(secondary_institution_id, provider['id'], username)
183+
logger.error(message)
184+
sentry.log_message(message)
185+
else:
186+
# SSO from primary institution only
187+
logger.info('Institution SSO: primary=[{}], secondary=[None], '
188+
'username=[{}]'.format(provider['id'], username))
189189

190190
# Use given name and family name to build full name if it is not provided
191191
if given_name and family_name and not fullname:

0 commit comments

Comments
 (0)