Skip to content

Commit 1c709f6

Browse files
authored
Merge pull request #11629 from Ostap-Zherebetskyi/feature/reactivate_institution_sso
[ENG-10289] [ENG-10289] [ENG-10404] Update institution model and related to support SSO Availability - Part 1
2 parents 7a54304 + 26efc85 commit 1c709f6

File tree

5 files changed

+70
-21
lines changed

5 files changed

+70
-21
lines changed

admin_tests/institutions/test_views.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ def test_institution_form(self):
139139
'name': 'New Name',
140140
'logo_name': 'awesome_logo.png',
141141
'domains': 'http://kris.biz/, http://www.little.biz/',
142-
'_id': 'newawesomeprov'
142+
'_id': 'newawesomeprov',
143+
'sso_availability': 'Public',
143144
}
144145
form = InstitutionForm(data=new_data)
145146
assert form.is_valid()
@@ -214,7 +215,8 @@ def test_monthly_reporter_called_on_create(self, mock_monthly_reporter_do):
214215
'email_domains': FakeList('domain_name', n=1),
215216
'orcid_record_verified_source': '',
216217
'delegation_protocol': '',
217-
'institutional_request_access_enabled': False
218+
'institutional_request_access_enabled': False,
219+
'sso_availability': 'Public',
218220
}
219221
form = InstitutionForm(data=data)
220222
assert form.is_valid()

osf/migrations/0036_institution_sso_in_progress.py

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.15 on 2026-03-13 11:11
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('osf', '0037_notification_refactor_post_release'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='institution',
15+
name='sso_availability',
16+
field=models.CharField(choices=[('Public', 'PUBLIC'), ('Unavailable', 'UNAVAILABLE'), ('Hidden', 'HIDDEN')], default='Hidden', max_length=15),
17+
),
18+
]

osf/models/institution.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ class SsoFilterCriteriaAction(Enum):
4646
CONTAINS = 'contains' # Type 2: SSO releases a multi-value attribute, of which one value matches
4747
IN = 'in' # Type 3: SSO releases a single-value attribute that have multiple valid values
4848

49+
class SSOAvailability(Enum):
50+
"""Defines 3 SSO availability states for institutions.
51+
"""
52+
PUBLIC = 'Public' # Active, has a delegation protocol and SSO setup has been verified
53+
UNAVAILABLE = 'Unavailable' # Does not have a delegation protocol
54+
HIDDEN = 'Hidden' # 1) Inactive and has a delegation protocol, or 2) active, has a delegation protocol and SSO setup is in-progress
55+
4956

5057
class InstitutionManager(models.Manager):
5158

@@ -79,6 +86,13 @@ class Institution(DirtyFieldsMixin, Loggable, ObjectIDMixin, BaseModel, Guardian
7986
default=''
8087
)
8188

89+
# Institution SSO availability
90+
sso_availability = models.CharField(
91+
choices=[(choice.value, choice.name) for choice in SSOAvailability],
92+
max_length=15,
93+
default=SSOAvailability.HIDDEN.value
94+
)
95+
8296
# Default Storage Region
8397
storage_regions = models.ManyToManyField(
8498
'addons_osfstorage.Region',
@@ -125,7 +139,6 @@ class Institution(DirtyFieldsMixin, Loggable, ObjectIDMixin, BaseModel, Guardian
125139
default='',
126140
help_text='Full URL where institutional admins can access archived metrics reports.',
127141
)
128-
sso_in_progress = models.BooleanField(default=False)
129142

130143
class Meta:
131144
# custom permissions for use in the OSF Admin App
@@ -238,6 +251,11 @@ def deactivate(self):
238251
"""
239252
if not self.deactivated:
240253
self.deactivated = timezone.now()
254+
if not self.delegation_protocol:
255+
self.sso_availability = SSOAvailability.UNAVAILABLE.value
256+
else:
257+
self.sso_availability = SSOAvailability.HIDDEN.value
258+
241259
self.save()
242260
# Django mangers aren't used when querying on related models. Thus, we can query
243261
# affiliated users and send notification emails after the institution has been deactivated.
@@ -252,6 +270,10 @@ def reactivate(self):
252270
"""
253271
if self.deactivated:
254272
self.deactivated = None
273+
if not self.delegation_protocol:
274+
self.sso_availability = SSOAvailability.UNAVAILABLE.value
275+
else:
276+
self.sso_availability = SSOAvailability.HIDDEN.value
255277
self.save()
256278
else:
257279
message = f'Action rejected - reactivating an active institution [{self._id}].'

osf_tests/test_institution.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ def test_deactivated_institution_in_all_institutions(self):
128128
institution.save()
129129
assert institution in Institution.objects.get_all_institutions()
130130

131+
def test_deactivate_sso_institution(self):
132+
institution = InstitutionFactory()
133+
institution.delegation_protocol = 'saml-shib'
134+
institution.save()
135+
with mock.patch.object(
136+
institution,
137+
'_send_deactivation_email',
138+
return_value=None
139+
) as mock__send_deactivation_email:
140+
institution.deactivate()
141+
assert institution.deactivated is not None
142+
assert mock__send_deactivation_email.called
143+
assert institution.sso_availability == 'Hidden'
144+
131145
def test_deactivate_institution(self):
132146
institution = InstitutionFactory()
133147
with mock.patch.object(
@@ -138,13 +152,24 @@ def test_deactivate_institution(self):
138152
institution.deactivate()
139153
assert institution.deactivated is not None
140154
assert mock__send_deactivation_email.called
155+
assert institution.sso_availability == 'Unavailable'
156+
157+
def test_reactivate_sso_institution(self):
158+
institution = InstitutionFactory()
159+
institution.delegation_protocol = 'saml-shib'
160+
institution.deactivated = timezone.now()
161+
institution.save()
162+
institution.reactivate()
163+
assert institution.deactivated is None
164+
assert institution.sso_availability == 'Hidden'
141165

142166
def test_reactivate_institution(self):
143167
institution = InstitutionFactory()
144168
institution.deactivated = timezone.now()
145169
institution.save()
146170
institution.reactivate()
147171
assert institution.deactivated is None
172+
assert institution.sso_availability == 'Unavailable'
148173

149174
def test_send_deactivation_email_call_count(self):
150175
institution = InstitutionFactory()

0 commit comments

Comments
 (0)