|
17 | 17 |
|
18 | 18 | from osf import features |
19 | 19 | from osf.models import Institution |
| 20 | +from osf.models.institution import SharedSsoAffiliationFilterCriteriaAction |
20 | 21 |
|
21 | 22 | from website.mails import send_mail, WELCOME_OSF4I |
22 | 23 | from website.settings import OSF_SUPPORT_EMAIL, DOMAIN |
23 | 24 |
|
24 | 25 | logger = logging.getLogger(__name__) |
25 | 26 |
|
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. |
28 | 29 | # |
29 | 30 | # '<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', |
51 | 35 | # } |
| 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. |
52 | 39 | # |
53 | 40 | INSTITUTION_SHARED_SSO_MAP = { |
54 | 41 | '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', |
60 | 52 | }, |
61 | 53 | } |
62 | 54 |
|
@@ -158,34 +150,42 @@ def authenticate(self, request): |
158 | 150 | secondary_institution = None |
159 | 151 | if provider['id'] in INSTITUTION_SHARED_SSO_MAP: |
160 | 152 | 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 |
184 | 163 | 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) |
187 | 166 | logger.error(message) |
188 | 167 | 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)) |
189 | 189 |
|
190 | 190 | # Use given name and family name to build full name if it is not provided |
191 | 191 | if given_name and family_name and not fullname: |
|
0 commit comments