diff --git a/admin/institutions/forms.py b/admin/institutions/forms.py index ccb7e9c4e77..1ccd3dc514f 100644 --- a/admin/institutions/forms.py +++ b/admin/institutions/forms.py @@ -1,5 +1,5 @@ from django import forms -from osf.models import Institution +from osf.models.institution import Institution, SSOAvailability class InstitutionForm(forms.ModelForm): @@ -10,6 +10,18 @@ class Meta: 'is_deleted', 'contributors', 'storage_regions', ] + def clean(self): + super().clean() + + if hasattr(self, 'cleaned_data') and self.changed_data: + if not self.cleaned_data['delegation_protocol']: + if self.cleaned_data['sso_availability'] != SSOAvailability.UNAVAILABLE.value: + self.add_error('sso_availability', 'Must be UNAVAILABLE when no protocol') + + elif self.cleaned_data['deactivated']: + if self.cleaned_data['sso_availability'] != SSOAvailability.HIDDEN.value: + self.add_error('sso_availability', 'Inactive must be HIDDEN') + class InstitutionalMetricsAdminRegisterForm(forms.Form): """ A form that finds an existing OSF User, and grants permissions to that diff --git a/admin/institutions/views.py b/admin/institutions/views.py index 536d916d937..bd690747b48 100644 --- a/admin/institutions/views.py +++ b/admin/institutions/views.py @@ -56,6 +56,7 @@ def get_context_data(self, *args, **kwargs): institution_dict = model_to_dict(institution) kwargs.setdefault('page_number', self.request.GET.get('page', '1')) kwargs['institution'] = institution_dict + kwargs['cas_login_url'] = institution.cas_login_url kwargs['logo_path'] = institution.logo_path kwargs['banner_path'] = institution.banner_path fields = institution_dict @@ -117,6 +118,16 @@ def get_context_data(self, *args, **kwargs): def get_success_url(self, *args, **kwargs): return reverse_lazy('institutions:detail', kwargs={'institution_id': self.kwargs.get('institution_id')}) + def post(self, request, *args, **kwargs): + # Override `post` method in `django.views.generic.edit.ProcessFormView` due to custom behavior + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + messages.error(request, form.errors) + return redirect('institutions:detail', institution_id=self.kwargs.get('institution_id')) + class InstitutionExport(PermissionRequiredMixin, View): permission_required = 'osf.view_institution' diff --git a/admin/templates/institutions/detail.html b/admin/templates/institutions/detail.html index 8c4a9e79e15..38b3ba2813d 100644 --- a/admin/templates/institutions/detail.html +++ b/admin/templates/institutions/detail.html @@ -1,7 +1,33 @@ {% extends "base.html" %} {% load static %} {% block top_includes %} - + + {% endblock %} {% load comment_extras %} {% block title %} @@ -34,6 +60,18 @@ {% if perms.osf.change_institution %} Manage Admins {% endif %} + {% if cas_login_url %} + + + {% endif %} @@ -169,5 +207,27 @@

Are you sure you want to run monthly report for this institution?

}); }); }); + + window.openCopyPopup = function(text) { + const modal = document.getElementById("copy-modal"); + const input = document.getElementById("copy-input"); + input.value = text; + modal.classList.add("show_modal"); + navigator.clipboard.writeText(text).catch(() => {}); + input.focus(); + input.select(); + }; + + window.closeCopyPopup = function() { + document.getElementById("copy-modal").classList.remove("show_modal"); + }; + + // Close on outside click + window.onclick = function(event) { + const modal = document.getElementById("copy-modal"); + if (event.target === modal) { + modal.classList.remove("show_modal"); + } + }; {% endblock %} diff --git a/admin/templates/institutions/list.html b/admin/templates/institutions/list.html index f990c778d25..47e6d09233f 100644 --- a/admin/templates/institutions/list.html +++ b/admin/templates/institutions/list.html @@ -20,6 +20,7 @@

List of Institutions

Name Description Status + SSO Availability @@ -37,6 +38,7 @@

List of Institutions

{% else %} DEACTIVATED {% endif %} + {{ institution.sso_availability }} {% endfor %} diff --git a/admin_tests/institutions/test_views.py b/admin_tests/institutions/test_views.py index 13cb1456ab9..1a8029ed088 100644 --- a/admin_tests/institutions/test_views.py +++ b/admin_tests/institutions/test_views.py @@ -139,11 +139,24 @@ def test_institution_form(self): 'name': 'New Name', 'logo_name': 'awesome_logo.png', 'domains': 'http://kris.biz/, http://www.little.biz/', - '_id': 'newawesomeprov' + '_id': 'newawesomeprov', + 'sso_availability': 'Unavailable', } form = InstitutionForm(data=new_data) assert form.is_valid() + def test_institution_form_invalid(self): + new_data = { + 'name': 'New Name', + 'logo_name': 'awesome_logo.png', + 'domains': 'http://kris.biz/, http://www.little.biz/', + '_id': 'newawesomeprov', + 'sso_availability': 'Public', + } + form = InstitutionForm(data=new_data) + assert not form.is_valid() + assert 'sso_availability' in form.errors + class TestInstitutionExport(AdminTestCase): def setUp(self): @@ -214,7 +227,8 @@ def test_monthly_reporter_called_on_create(self, mock_monthly_reporter_do): 'email_domains': FakeList('domain_name', n=1), 'orcid_record_verified_source': '', 'delegation_protocol': '', - 'institutional_request_access_enabled': False + 'institutional_request_access_enabled': False, + 'sso_availability': 'Unavailable', } form = InstitutionForm(data=data) assert form.is_valid() diff --git a/api/institutions/serializers.py b/api/institutions/serializers.py index 6f4bc4f9e15..fc1e48cefdf 100644 --- a/api/institutions/serializers.py +++ b/api/institutions/serializers.py @@ -29,10 +29,12 @@ class InstitutionSerializer(JSONAPISerializer): 'id', 'name', 'auth_url', + 'sso_availability', ]) name = ser.CharField(read_only=True) id = ser.CharField(read_only=True, source='_id') + sso_availability = ser.CharField(read_only=True) description = ser.CharField(read_only=True) auth_url = ser.CharField(read_only=True) iri = ser.CharField(read_only=True, source='identifier_domain') diff --git a/api/institutions/views.py b/api/institutions/views.py index a3c0f93d0c8..d653f5b4e77 100644 --- a/api/institutions/views.py +++ b/api/institutions/views.py @@ -73,6 +73,9 @@ class InstitutionList(JSONAPIBaseView, generics.ListAPIView, ListFilterMixin): base_permissions.TokenHasScope, ) + # Adding sso_availability to MULTIPLE_VALUES_FIELDS to allow filtering institutions by multiple sso_availability values, e.g. ?filter[sso_availability]=[Unavailable,Hidden] + MULTIPLE_VALUES_FIELDS = ListFilterMixin.MULTIPLE_VALUES_FIELDS + ['sso_availability'] + required_read_scopes = [CoreScopes.INSTITUTION_READ] required_write_scopes = [CoreScopes.NULL] model_class = Institution @@ -85,7 +88,9 @@ class InstitutionList(JSONAPIBaseView, generics.ListAPIView, ListFilterMixin): ordering = ('name',) def get_default_queryset(self): - return Institution.objects.filter(_id__isnull=False, is_deleted=False) + if 'filter[sso_availability]' in self.request.query_params: + return Institution.objects.filter(_id__isnull=False, is_deleted=False) + return Institution.objects.get_non_hidden_institutions().filter(_id__isnull=False, is_deleted=False) # overrides ListAPIView def get_queryset(self): diff --git a/api_tests/institutions/views/test_institution_list.py b/api_tests/institutions/views/test_institution_list.py index 74cb0b6bc8f..60e63056b7a 100644 --- a/api_tests/institutions/views/test_institution_list.py +++ b/api_tests/institutions/views/test_institution_list.py @@ -15,6 +15,10 @@ def institution_one(self): def institution_two(self): return InstitutionFactory() + @pytest.fixture() + def institution_three(self): + return InstitutionFactory() + @pytest.fixture() def url_institution(self): return f'/{API_BASE}institutions/' @@ -47,3 +51,53 @@ def test_does_not_return_deleted_institution( assert len(res.json['data']) == 1 assert institution_one._id not in ids assert institution_two._id in ids + + def test_sso_availability_filter( + self, app, institution_one, institution_two, institution_three, url_institution + ): + institution_one.sso_availability = 'Unavailable' + institution_one.save() + + institution_two.sso_availability = 'Public' + institution_two.save() + + institution_three.sso_availability = 'Hidden' + institution_three.save() + + res = app.get(f'{url_institution}?filter[sso_availability]=[Unavailable]') + assert res.status_code == 200 + + ids = [each['id'] for each in res.json['data']] + assert len(res.json['data']) == 1 + assert institution_one._id in ids + assert institution_two._id not in ids + + res = app.get(f'{url_institution}?filter[sso_availability]=[Unavailable,Hidden]') + assert res.status_code == 200 + + ids = [each['id'] for each in res.json['data']] + assert len(res.json['data']) == 2 + assert institution_one._id in ids + assert institution_three._id in ids + assert institution_two._id not in ids + + def test_default_filter_excludes_institutions_with_sso_availability_hidden( + self, app, institution_one, institution_two, institution_three, url_institution + ): + institution_one.sso_availability = 'Unavailable' + institution_one.save() + + institution_two.sso_availability = 'Public' + institution_two.save() + + institution_three.sso_availability = 'Hidden' + institution_three.save() + + res = app.get(url_institution) + assert res.status_code == 200 + + ids = [each['id'] for each in res.json['data']] + assert len(res.json['data']) == 2 + assert institution_one._id in ids + assert institution_two._id in ids + assert institution_three._id not in ids diff --git a/framework/auth/views.py b/framework/auth/views.py index 2adeb00b3b6..59b2a207af4 100644 --- a/framework/auth/views.py +++ b/framework/auth/views.py @@ -1202,6 +1202,10 @@ def validate_next_url(next_url): :return: True if valid, False otherwise """ + # allow redirection to angular locally + if settings.LOCAL_ANGULAR_URL in next_url and settings.DEBUG_MODE: + return True + # disable external domain using `//`: the browser allows `//` as a shortcut for non-protocol specific requests # like http:// or https:// depending on the use of SSL on the page already. if next_url.startswith('//'): diff --git a/osf/management/commands/backfill_sso_availability.py b/osf/management/commands/backfill_sso_availability.py new file mode 100644 index 00000000000..d86e9362ff6 --- /dev/null +++ b/osf/management/commands/backfill_sso_availability.py @@ -0,0 +1,79 @@ +from django.core.management.base import BaseCommand +from django.db import transaction +from django.db.models import Q + +from osf.models.institution import Institution, SSOAvailability, IntegrationType + + +class Command(BaseCommand): + help = 'Backfill sso_availability using fast DB-level updates' + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Show how many rows would be updated without applying changes', + ) + + def handle(self, *args, **options): + dry_run = options['dry_run'] + + # Build querysets + qs_no_protocol = Institution.objects.filter( + delegation_protocol=IntegrationType.NONE.value + ).exclude( + sso_availability=SSOAvailability.UNAVAILABLE.value + ) + + qs_inactive_with_protocol = Institution.objects.filter( + ~Q(delegation_protocol=IntegrationType.NONE.value), + deactivated__isnull=False + ).exclude( + sso_availability=SSOAvailability.HIDDEN.value + ) + + qs_active_with_protocol = Institution.objects.filter( + ~Q(delegation_protocol=IntegrationType.NONE.value), + deactivated__isnull=True + ).exclude( + sso_availability=SSOAvailability.PUBLIC.value + ) + + count_no_protocol = qs_no_protocol.count() + count_inactive = qs_inactive_with_protocol.count() + count_active = qs_active_with_protocol.count() + + total = count_no_protocol + count_inactive + count_active + + self.stdout.write('Planned updates:') + self.stdout.write(f" No protocol → UNAVAILABLE: {count_no_protocol}") + self.stdout.write(f" Inactive + protocol → HIDDEN: {count_inactive}") + self.stdout.write(f" Active + protocol → PUBLIC: {count_active}") + self.stdout.write(f" TOTAL: {total}") + + if dry_run: + self.stdout.write(self.style.WARNING('Dry run, no changes applied.')) + return + + with transaction.atomic(): + updated_no_protocol = qs_no_protocol.update( + sso_availability=SSOAvailability.UNAVAILABLE.value + ) + + updated_inactive = qs_inactive_with_protocol.update( + sso_availability=SSOAvailability.HIDDEN.value + ) + + updated_active = qs_active_with_protocol.update( + sso_availability=SSOAvailability.PUBLIC.value + ) + + self.stdout.write( + self.style.SUCCESS( + 'Done:\n' + f" UNAVAILABLE: {updated_no_protocol}\n" + f" HIDDEN: {updated_inactive}\n" + f" PUBLIC: {updated_active}\n" + f" TOTAL: {updated_no_protocol + updated_inactive + updated_active}" + ) + ) diff --git a/osf/migrations/0038_institution_sso_availability.py b/osf/migrations/0038_institution_sso_availability.py new file mode 100644 index 00000000000..c4be5de4001 --- /dev/null +++ b/osf/migrations/0038_institution_sso_availability.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2026-03-13 11:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('osf', '0037_notification_refactor_post_release'), + ] + + operations = [ + migrations.AddField( + model_name='institution', + name='sso_availability', + field=models.CharField(choices=[('Public', 'PUBLIC'), ('Unavailable', 'UNAVAILABLE'), ('Hidden', 'HIDDEN')], default='Hidden', max_length=15), + ), + ] diff --git a/osf/models/institution.py b/osf/models/institution.py index 3671e7bef1f..379f301a966 100644 --- a/osf/models/institution.py +++ b/osf/models/institution.py @@ -24,6 +24,7 @@ from .validators import validate_email from osf.utils.fields import NonNaiveDateTimeField, LowercaseEmailField from website import settings as website_settings +from urllib.parse import quote logger = logging.getLogger(__name__) @@ -46,6 +47,13 @@ class SsoFilterCriteriaAction(Enum): CONTAINS = 'contains' # Type 2: SSO releases a multi-value attribute, of which one value matches IN = 'in' # Type 3: SSO releases a single-value attribute that have multiple valid values +class SSOAvailability(Enum): + """Defines 3 SSO availability states for institutions. + """ + PUBLIC = 'Public' # Active, has a delegation protocol and SSO setup has been verified + UNAVAILABLE = 'Unavailable' # Does not have a delegation protocol + HIDDEN = 'Hidden' # 1) Inactive and has a delegation protocol, or 2) active, has a delegation protocol and SSO setup is in-progress + class InstitutionManager(models.Manager): @@ -55,6 +63,9 @@ def get_queryset(self): def get_all_institutions(self): return super().get_queryset() + def get_non_hidden_institutions(self): + return super().get_queryset().filter(deactivated__isnull=True, sso_availability__in=[SSOAvailability.PUBLIC.value, SSOAvailability.UNAVAILABLE.value]) + class Institution(DirtyFieldsMixin, Loggable, ObjectIDMixin, BaseModel, GuardianMixin): objects = InstitutionManager() @@ -79,6 +90,13 @@ class Institution(DirtyFieldsMixin, Loggable, ObjectIDMixin, BaseModel, Guardian default='' ) + # Institution SSO availability + sso_availability = models.CharField( + choices=[(choice.value, choice.name) for choice in SSOAvailability], + max_length=15, + default=SSOAvailability.HIDDEN.value + ) + # Default Storage Region storage_regions = models.ManyToManyField( 'addons_osfstorage.Region', @@ -194,6 +212,17 @@ def banner_path(self): except InstitutionAssetFile.DoesNotExist: return '/static/img/institutions/banners/placeholder-banner.png' + @property + def cas_login_url(self): + if self.delegation_protocol == IntegrationType.NONE.value: + return None + if 'localhost' in website_settings.DOMAIN: + next_param = quote(website_settings.PROTOCOL + website_settings.LOCAL_ANGULAR_URL, safe='') + else: + next_param = quote(website_settings.DOMAIN, safe='') + service_url = quote(f'{website_settings.DOMAIN}login?next={next_param}', safe='') + return f'{website_settings.CAS_SERVER_URL}/login?campaign=institution&institutionId={self._id}&service={service_url}' + def update_search(self): from website.search.search import update_institution from website.search.exceptions import SearchUnavailableError @@ -237,6 +266,11 @@ def deactivate(self): """ if not self.deactivated: self.deactivated = timezone.now() + if not self.delegation_protocol: + self.sso_availability = SSOAvailability.UNAVAILABLE.value + else: + self.sso_availability = SSOAvailability.HIDDEN.value + self.save() # Django mangers aren't used when querying on related models. Thus, we can query # affiliated users and send notification emails after the institution has been deactivated. @@ -251,6 +285,10 @@ def reactivate(self): """ if self.deactivated: self.deactivated = None + if not self.delegation_protocol: + self.sso_availability = SSOAvailability.UNAVAILABLE.value + else: + self.sso_availability = SSOAvailability.HIDDEN.value self.save() else: message = f'Action rejected - reactivating an active institution [{self._id}].' diff --git a/osf_tests/factories.py b/osf_tests/factories.py index 0b357aed1aa..0d5e5ad0c32 100644 --- a/osf_tests/factories.py +++ b/osf_tests/factories.py @@ -28,6 +28,7 @@ from osf import models from osf.models.sanctions import Sanction from osf.models.storage import PROVIDER_ASSET_NAME_CHOICES +from osf.models.institution import SSOAvailability from osf.utils.names import impute_names_model from osf.utils.workflows import ( DefaultStates, @@ -258,6 +259,7 @@ class InstitutionFactory(DjangoModelFactory): orcid_record_verified_source = '' delegation_protocol = '' institutional_request_access_enabled = False + sso_availability = SSOAvailability.PUBLIC.value class Meta: model = models.Institution diff --git a/osf_tests/test_institution.py b/osf_tests/test_institution.py index 039b0ce04dd..867723cf291 100644 --- a/osf_tests/test_institution.py +++ b/osf_tests/test_institution.py @@ -128,6 +128,20 @@ def test_deactivated_institution_in_all_institutions(self): institution.save() assert institution in Institution.objects.get_all_institutions() + def test_deactivate_sso_institution(self): + institution = InstitutionFactory() + institution.delegation_protocol = 'saml-shib' + institution.save() + with mock.patch.object( + institution, + '_send_deactivation_email', + return_value=None + ) as mock__send_deactivation_email: + institution.deactivate() + assert institution.deactivated is not None + assert mock__send_deactivation_email.called + assert institution.sso_availability == 'Hidden' + def test_deactivate_institution(self): institution = InstitutionFactory() with mock.patch.object( @@ -138,6 +152,16 @@ def test_deactivate_institution(self): institution.deactivate() assert institution.deactivated is not None assert mock__send_deactivation_email.called + assert institution.sso_availability == 'Unavailable' + + def test_reactivate_sso_institution(self): + institution = InstitutionFactory() + institution.delegation_protocol = 'saml-shib' + institution.deactivated = timezone.now() + institution.save() + institution.reactivate() + assert institution.deactivated is None + assert institution.sso_availability == 'Hidden' def test_reactivate_institution(self): institution = InstitutionFactory() @@ -145,6 +169,7 @@ def test_reactivate_institution(self): institution.save() institution.reactivate() assert institution.deactivated is None + assert institution.sso_availability == 'Unavailable' def test_send_deactivation_email_call_count(self): institution = InstitutionFactory() diff --git a/scripts/populate_institutions.py b/scripts/populate_institutions.py index f46f7c67113..64bf59bd2b9 100644 --- a/scripts/populate_institutions.py +++ b/scripts/populate_institutions.py @@ -12,13 +12,13 @@ from website import settings from website.app import init_app -from osf.models import Institution +from osf.models.institution import Institution, SSOAvailability, IntegrationType from website.search.search import update_institution logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) -ENVS = ['prod', 'stage', 'stage2', 'stage3', 'test', 'local'] +ENVS = ['prod', 'stage', 'stage2', 'stage3', 'test', 'local', 'auto_generated'] # TODO: Store only the Entity IDs in OSF DB and move the URL building process to CAS SHIBBOLETH_SP_LOGIN = f'{settings.CAS_SERVER_URL}/Shibboleth.sso/Login?entityID={{}}' @@ -37,7 +37,10 @@ def encode_uri_component(val): def update_or_create(inst_data): - inst = Institution.load(inst_data['_id']) + try: + inst = Institution.objects.get_all_institutions().get(_id=inst_data['_id']) + except Institution.DoesNotExist: + inst = None if inst: for key, val in inst_data.items(): setattr(inst, key, val) @@ -53,6 +56,53 @@ def update_or_create(inst_data): return inst, True +PROTOCOL_MAP = { + IntegrationType.SAML_SHIBBOLETH.value: 'SAML', + IntegrationType.CAS_PAC4J.value: 'CAS', + IntegrationType.OAUTH_PAC4J.value: 'OAuth', + IntegrationType.AFFILIATION_VIA_ORCID.value: 'ORCiD', + IntegrationType.NONE.value: 'None', +} + + +DEACTIVATED_STATES = [ + None, + '2026-01-01T00:00:00+00:00', +] + + +def get_valid_sso_states(protocol, deactivated): + is_active = deactivated is None + if not protocol: + return [SSOAvailability.UNAVAILABLE.value] + if not is_active: + return [SSOAvailability.HIDDEN.value] + return [SSOAvailability.PUBLIC.value, SSOAvailability.HIDDEN.value] + + +def generate_test_institutions(): + institutions = [] + + for protocol in PROTOCOL_MAP.keys(): + for deactivated in DEACTIVATED_STATES: + for availability in get_valid_sso_states(protocol, deactivated): + _id = f"{PROTOCOL_MAP[protocol]}_{availability}_{'a' if deactivated is None else 'i'}".lower() + institutions.append({ + '_id': _id, + 'name': f'Test Institution [{PROTOCOL_MAP[protocol] if protocol else "None"} {availability} {"Inactive" if deactivated else "Active"}]', + 'description': f'Description for {PROTOCOL_MAP[protocol] if protocol else "None"} {availability} {"Inactive" if deactivated else "Active"}', + 'login_url': SHIBBOLETH_SP_LOGIN.format(encode_uri_component(f'{_id}-entity-id')) if protocol == IntegrationType.SAML_SHIBBOLETH.value else None, + 'logout_url': SHIBBOLETH_SP_LOGOUT.format(encode_uri_component(f'{settings.DOMAIN}{_id}')) if protocol == IntegrationType.SAML_SHIBBOLETH.value else None, + 'domains': [], + 'email_domains': [f'{_id}.osf.io'] if not protocol else [], + 'delegation_protocol': protocol, + 'sso_availability': availability, + 'deactivated': deactivated, + }) + + return institutions + + def main(default_args=False): if default_args: @@ -67,7 +117,12 @@ def main(default_args=False): if not server_env or server_env not in ENVS: logger.error(f'A valid environment must be specified: {ENVS}') sys.exit(1) - institutions = INSTITUTIONS[server_env] + + if server_env == 'auto_generated': + logger.info('Generating institutions with all combinations of protocol, availability, and deactivated states for testing purposes.') + institutions = generate_test_institutions() + else: + institutions = INSTITUTIONS[server_env] if not update_all and not update_ids: logger.error('Nothing to update or create. Please either specify a list of institutions ' @@ -101,6 +156,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['a2jlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'albion', @@ -113,6 +169,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'asu', @@ -125,6 +182,7 @@ def main(default_args=False): 'domains': ['osf.asu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'brown', @@ -135,6 +193,7 @@ def main(default_args=False): 'domains': ['osf.brown.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'bt', @@ -145,6 +204,7 @@ def main(default_args=False): 'domains': ['osf.boystownhospital.org'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'bu', @@ -155,6 +215,7 @@ def main(default_args=False): 'domains': ['osf.bu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'busara', @@ -165,6 +226,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['busaracenter.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'callutheran', @@ -175,6 +237,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'capolicylab', @@ -185,6 +248,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['capolicylab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'cfa', @@ -195,6 +259,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['cfa.harvard.edu'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'clrn', @@ -205,6 +270,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['characterlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'cmu', @@ -219,6 +285,7 @@ def main(default_args=False): 'domains': ['osf.library.cmu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'colorado', @@ -229,6 +296,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cord', @@ -239,6 +307,7 @@ def main(default_args=False): 'domains': ['osf.cord.edu'], 'email_domains': [], 'delegation_protocol': 'cas-pac4j', + 'sso_availability': 'Public', }, { '_id': 'cornell', @@ -249,6 +318,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cos', @@ -259,6 +329,7 @@ def main(default_args=False): 'domains': ['osf.cos.io'], 'email_domains': ['cos.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'csic', @@ -269,6 +340,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cwru', @@ -279,6 +351,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'duke', @@ -289,6 +362,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ecu', @@ -299,6 +373,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'esip', @@ -309,6 +384,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['esipfed.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'eur', @@ -324,6 +400,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ferris', @@ -334,6 +411,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'fsu', @@ -344,6 +422,7 @@ def main(default_args=False): 'domains': ['osf.fsu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gatech', @@ -354,6 +433,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gmu', @@ -364,6 +444,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gwu', @@ -374,6 +455,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'harvard', @@ -384,6 +466,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ibhri', @@ -394,6 +477,7 @@ def main(default_args=False): 'domains': ['osf.ibhri.org'], 'email_domains': ['ibhri.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'icarehb', @@ -404,6 +488,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['icarehb.com'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'icer', @@ -414,6 +499,7 @@ def main(default_args=False): 'domains': ['osf.icer-review.org'], 'email_domains': ['icer-review.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'igdore', @@ -427,6 +513,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['igdore.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'iit', @@ -437,6 +524,7 @@ def main(default_args=False): 'domains': ['osf.iit.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'itb', @@ -447,6 +535,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jhu', @@ -457,6 +546,7 @@ def main(default_args=False): 'domains': ['osf.data.jhu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jmu', @@ -467,6 +557,7 @@ def main(default_args=False): 'domains': ['osf.jmu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jpal', @@ -477,6 +568,7 @@ def main(default_args=False): 'domains': ['osf.povertyactionlab.org'], 'email_domains': ['povertyactionlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'kuleuven', @@ -487,6 +579,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ljaf', @@ -497,6 +590,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['arnoldfoundation.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'mit', @@ -507,6 +601,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'mq', @@ -517,6 +612,7 @@ def main(default_args=False): 'domains': ['osf.mq.edu.au'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nationalmaglab', @@ -527,6 +623,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nesta', @@ -537,6 +634,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nd', @@ -547,6 +645,7 @@ def main(default_args=False): 'domains': ['osf.nd.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nyu', @@ -557,6 +656,7 @@ def main(default_args=False): 'domains': ['osf.nyu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'okstate', @@ -567,6 +667,7 @@ def main(default_args=False): 'domains': ['osf.library.okstate.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ou', @@ -577,6 +678,7 @@ def main(default_args=False): 'domains': ['osf.ou.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'oxford', @@ -587,6 +689,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'via-orcid', + 'sso_availability': 'Public', 'orcid_record_verified_source': 'ORCID Integration at the University of Oxford', }, { @@ -598,6 +701,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'purdue', @@ -608,6 +712,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'sc', @@ -620,6 +725,7 @@ def main(default_args=False): 'domains': ['osf.sc.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'temple', @@ -630,6 +736,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'thepolicylab', @@ -640,6 +747,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'thelabatdc', @@ -650,6 +758,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['dc.gov'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'theworks', @@ -660,6 +769,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['theworks.info'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'tufts', @@ -670,6 +780,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ua', @@ -680,6 +791,7 @@ def main(default_args=False): 'domains': ['osf.arizona.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ubc', @@ -690,6 +802,7 @@ def main(default_args=False): 'domains': ['osf.openscience.ubc.ca'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uc', @@ -700,6 +813,7 @@ def main(default_args=False): 'domains': ['osf.uc.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucla', @@ -710,6 +824,7 @@ def main(default_args=False): 'domains': ['osf.ucla.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucsd', @@ -720,6 +835,7 @@ def main(default_args=False): 'domains': ['osf.ucsd.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucr', @@ -730,6 +846,7 @@ def main(default_args=False): 'domains': ['osf.ucr.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uct', @@ -740,6 +857,7 @@ def main(default_args=False): 'domains': ['osf.uct.ac.za'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ugent', @@ -750,6 +868,7 @@ def main(default_args=False): 'domains': ['osf.ugent.be'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ugoe', @@ -760,6 +879,7 @@ def main(default_args=False): 'domains': ['osf.uni-goettingen.de'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'umb', @@ -770,6 +890,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'umd', @@ -780,6 +901,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'unc', @@ -790,6 +912,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'universityofkent', @@ -800,6 +923,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uol', @@ -810,6 +934,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uom', @@ -820,6 +945,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'usc', @@ -830,6 +956,7 @@ def main(default_args=False): 'domains': ['osf.usc.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ush', @@ -840,6 +967,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['uvers.ac.id'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'utdallas', @@ -850,6 +978,7 @@ def main(default_args=False): 'domains': ['osf.utdallas.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uva', @@ -860,6 +989,7 @@ def main(default_args=False): 'domains': ['osf.virginia.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uw', @@ -870,6 +1000,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uwstout', @@ -880,6 +1011,7 @@ def main(default_args=False): 'domains': ['open.uwstout.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vcu', @@ -890,6 +1022,7 @@ def main(default_args=False): 'domains': ['osf.research.vcu.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vt', @@ -900,6 +1033,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vua', @@ -910,6 +1044,7 @@ def main(default_args=False): 'domains': ['osf.vu.nl'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'wustl', @@ -920,6 +1055,7 @@ def main(default_args=False): 'domains': ['osf.wustl.edu'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, ], 'stage': [ @@ -932,6 +1068,7 @@ def main(default_args=False): 'domains': ['staging-osf.cos.io'], 'email_domains': ['cos.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'nd', @@ -942,6 +1079,7 @@ def main(default_args=False): 'domains': ['staging-osf-nd.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'google', @@ -952,6 +1090,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['gmail.com'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'yahoo', @@ -961,6 +1100,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['yahoo.com'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'oxford', @@ -971,6 +1111,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'via-orcid', + 'sso_availability': 'Public', 'orcid_record_verified_source': 'ORCID Integration at the University of Oxford', }, { @@ -983,6 +1124,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'via-orcid', + 'sso_availability': 'Public', 'orcid_record_verified_source': 'OSF Integration', }, ], @@ -996,6 +1138,7 @@ def main(default_args=False): 'domains': ['staging2-osf.cos.io'], 'email_domains': ['cos.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, ], 'stage3': [ @@ -1008,6 +1151,7 @@ def main(default_args=False): 'domains': ['staging3-osf.cos.io'], 'email_domains': ['cos.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, ], 'test': [ @@ -1020,6 +1164,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'a2jlab', @@ -1030,6 +1175,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['a2jlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'albion', @@ -1042,6 +1188,7 @@ def main(default_args=False): 'domains': ['test-osf-ablbion.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'asu', @@ -1054,6 +1201,7 @@ def main(default_args=False): 'domains': ['test-osf-asu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'brown', @@ -1064,6 +1212,7 @@ def main(default_args=False): 'domains': ['test-osf-brown.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'bt', @@ -1074,6 +1223,7 @@ def main(default_args=False): 'domains': ['test-osf-bt.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'bu', @@ -1084,6 +1234,7 @@ def main(default_args=False): 'domains': ['test-osf-bu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'busara', @@ -1094,6 +1245,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['busaracenter.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'callutheran', @@ -1104,6 +1256,7 @@ def main(default_args=False): 'domains': ['test-osf-callutheran.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'callutheran2', @@ -1114,6 +1267,7 @@ def main(default_args=False): 'domains': ['test-osf-callutheran2.cos.io'], 'email_domains': [], 'delegation_protocol': 'cas-pac4j', + 'sso_availability': 'Public', }, { '_id': 'capolicylab', @@ -1124,6 +1278,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['capolicylab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'cfa', @@ -1134,6 +1289,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['cfa.harvard.edu'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'clrn', @@ -1144,6 +1300,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['characterlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'cmu', @@ -1158,6 +1315,7 @@ def main(default_args=False): 'domains': ['test-osf-cmu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'colorado', @@ -1168,6 +1326,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cornell', @@ -1178,6 +1337,7 @@ def main(default_args=False): 'domains': ['test-osf-cornell.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cord', @@ -1188,6 +1348,7 @@ def main(default_args=False): 'domains': ['test-osf-cord.cos.io'], 'email_domains': [], 'delegation_protocol': 'cas-pac4j', + 'sso_availability': 'Public', }, { '_id': 'cos', @@ -1198,6 +1359,7 @@ def main(default_args=False): 'domains': ['test-osf.cos.io'], 'email_domains': ['cos.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'csic', @@ -1208,6 +1370,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'cwru', @@ -1218,6 +1381,7 @@ def main(default_args=False): 'domains': ['test-osf-cwru.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'duke', @@ -1228,6 +1392,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ecu', @@ -1238,6 +1403,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'esip', @@ -1248,6 +1414,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['esipfed.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'eur', @@ -1263,6 +1430,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ferris', @@ -1273,6 +1441,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'fsu', @@ -1283,6 +1452,7 @@ def main(default_args=False): 'domains': ['test-osf-fsu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gatech', @@ -1293,6 +1463,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gmu', @@ -1303,6 +1474,7 @@ def main(default_args=False): 'domains': ['test-osf-gmu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'gwu', @@ -1313,6 +1485,7 @@ def main(default_args=False): 'domains': ['test-osf-gwu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'harvard', @@ -1323,6 +1496,7 @@ def main(default_args=False): 'domains': ['test-osf-harvard.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ibhri', @@ -1333,6 +1507,7 @@ def main(default_args=False): 'domains': ['test-osf-ibhri.cos.io'], 'email_domains': ['ibhri.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'icarehb', @@ -1343,6 +1518,7 @@ def main(default_args=False): 'domains': ['test-osf-icarehb.cos.io'], 'email_domains': ['icarehb.com'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'icer', @@ -1353,6 +1529,7 @@ def main(default_args=False): 'domains': ['test-osf-icer.cos.io'], 'email_domains': ['icer-review.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'igdore', @@ -1366,6 +1543,7 @@ def main(default_args=False): 'domains': ['test-osf-icer.igdore.io'], 'email_domains': ['igdore.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'iit', @@ -1376,6 +1554,7 @@ def main(default_args=False): 'domains': ['test-osf-iit.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'itb', @@ -1386,6 +1565,7 @@ def main(default_args=False): 'domains': ['test-osf-itb.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jhu', @@ -1396,6 +1576,7 @@ def main(default_args=False): 'domains': ['test-osf-jhu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jmu', @@ -1406,6 +1587,7 @@ def main(default_args=False): 'domains': ['test-osf-jmu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'jpal', @@ -1416,6 +1598,7 @@ def main(default_args=False): 'domains': ['test-osf-jpal.cos.io'], 'email_domains': ['povertyactionlab.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'kuleuven', @@ -1426,6 +1609,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ljaf', @@ -1436,6 +1620,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['arnoldfoundation.org'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'mit', @@ -1446,6 +1631,7 @@ def main(default_args=False): 'domains': ['test-osf-mit.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'mq', @@ -1456,6 +1642,7 @@ def main(default_args=False): 'domains': ['test-osf-mq.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nationalmaglab', @@ -1466,6 +1653,7 @@ def main(default_args=False): 'domains': ['test-osf-nationalmaglab.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nesta', @@ -1476,6 +1664,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nd', @@ -1486,6 +1675,7 @@ def main(default_args=False): 'domains': ['test-osf-nd.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'nyu', @@ -1496,6 +1686,7 @@ def main(default_args=False): 'domains': ['test-osf-nyu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'okstate', @@ -1506,6 +1697,7 @@ def main(default_args=False): 'domains': ['test-osf-library-okstate.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ou', @@ -1516,6 +1708,7 @@ def main(default_args=False): 'domains': ['test-osf-ou.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'oxford', @@ -1526,6 +1719,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'via-orcid', + 'sso_availability': 'Public', 'orcid_record_verified_source': 'ORCID Integration at the University of Oxford', }, { @@ -1537,6 +1731,7 @@ def main(default_args=False): 'domains': ['test-osf-pu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'purdue', @@ -1547,6 +1742,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'sc', @@ -1559,6 +1755,7 @@ def main(default_args=False): 'domains': ['test-osf-sc.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'temple', @@ -1569,6 +1766,7 @@ def main(default_args=False): 'domains': ['test-osf-temple.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'thepolicylab', @@ -1579,6 +1777,7 @@ def main(default_args=False): 'domains': ['test-osf-thepolicylab.cos.io'], 'email_domains': ['policylab.io'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'thelabatdc', @@ -1589,6 +1788,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['dc.gov'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'theworks', @@ -1599,6 +1799,7 @@ def main(default_args=False): 'domains': [], 'email_domains': ['theworks.info'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'tufts', @@ -1609,6 +1810,7 @@ def main(default_args=False): 'domains': ['test-osf-tufts.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ua', @@ -1619,6 +1821,7 @@ def main(default_args=False): 'domains': ['test-osf-ua.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ubc', @@ -1629,6 +1832,7 @@ def main(default_args=False): 'domains': ['test-osf-ubc.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uc', @@ -1639,6 +1843,7 @@ def main(default_args=False): 'domains': ['test-osf-uc.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucla', @@ -1649,6 +1854,7 @@ def main(default_args=False): 'domains': ['test-osf-ucla.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucsd', @@ -1659,6 +1865,7 @@ def main(default_args=False): 'domains': ['test-osf-ucsd.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ucr', @@ -1669,6 +1876,7 @@ def main(default_args=False): 'domains': ['test-osf-ucr.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uct', @@ -1679,6 +1887,7 @@ def main(default_args=False): 'domains': ['test-osf-uct.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'umb', @@ -1689,6 +1898,7 @@ def main(default_args=False): 'domains': ['test-osf-umb.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'umd', @@ -1699,6 +1909,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ugent', @@ -1709,6 +1920,7 @@ def main(default_args=False): 'domains': ['test-osf-ugent.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ugoe', @@ -1719,6 +1931,7 @@ def main(default_args=False): 'domains': ['test-osf-ugoe.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uit', @@ -1731,6 +1944,7 @@ def main(default_args=False): 'domains': ['test-osf-uit.cos.io'], 'email_domains': ['uit.no'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'unc', @@ -1741,6 +1955,7 @@ def main(default_args=False): 'domains': ['test-osf-unc.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'universityofkent', @@ -1751,6 +1966,7 @@ def main(default_args=False): 'domains': ['test-osf-universityofkent.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uol', @@ -1761,6 +1977,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uom', @@ -1771,6 +1988,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'usc', @@ -1781,6 +1999,7 @@ def main(default_args=False): 'domains': ['test-osf-usc.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'ush', @@ -1791,6 +2010,7 @@ def main(default_args=False): 'domains': ['test-osf-ush.cos.io'], 'email_domains': ['uvers.ac.id'], 'delegation_protocol': '', + 'sso_availability': 'Unavailable', }, { '_id': 'utdallas', @@ -1801,6 +2021,7 @@ def main(default_args=False): 'domains': ['test-osf-utdallas.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uva', @@ -1811,6 +2032,7 @@ def main(default_args=False): 'domains': ['test-osf-virginia.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uw', @@ -1821,6 +2043,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'uwstout', @@ -1831,6 +2054,7 @@ def main(default_args=False): 'domains': ['test-osf-uwstout.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vcu', @@ -1841,6 +2065,7 @@ def main(default_args=False): 'domains': ['test-osf-research-vcu.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vt', @@ -1851,6 +2076,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'vua', @@ -1861,6 +2087,7 @@ def main(default_args=False): 'domains': ['test-osf-vua.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'wustl', @@ -1871,6 +2098,7 @@ def main(default_args=False): 'domains': ['test-osf-wustl.cos.io'], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, ], 'local': [ @@ -1884,6 +2112,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'cas-pac4j', + 'sso_availability': 'Public', }, { '_id': 'osftype1', @@ -1895,6 +2124,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'via-orcid', + 'sso_availability': 'Public', 'orcid_record_verified_source': 'OSF Integration', }, { @@ -1907,6 +2137,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', 'orcid_record_verified_source': '', }, { @@ -1920,6 +2151,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'osftype4', @@ -1932,6 +2164,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'osftype5', @@ -1944,6 +2177,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', 'orcid_record_verified_source': '', }, { @@ -1957,6 +2191,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'osftype7', @@ -1969,6 +2204,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'osftype8', @@ -1981,6 +2217,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, { '_id': 'osftype9', @@ -1993,6 +2230,7 @@ def main(default_args=False): 'domains': [], 'email_domains': [], 'delegation_protocol': 'saml-shib', + 'sso_availability': 'Public', }, ], } diff --git a/tests/test_auth_views.py b/tests/test_auth_views.py index 7b24e286c15..75ef5de497a 100644 --- a/tests/test_auth_views.py +++ b/tests/test_auth_views.py @@ -584,6 +584,17 @@ def test_next_url_login_with_auth(self): assert data.get('status_code') == http_status.HTTP_302_FOUND assert data.get('next_url') == self.next_url + def test_next_url_angular_login_with_auth(self): + data = login_and_register_handler(self.auth, next_url=settings.LOCAL_ANGULAR_URL) + assert data.get('status_code') == http_status.HTTP_302_FOUND + assert data.get('next_url') == settings.LOCAL_ANGULAR_URL + + def test_next_url_angular_login_without_auth(self): + request.url = web_url_for('auth_login', next=settings.LOCAL_ANGULAR_URL, _absolute=True) + data = login_and_register_handler(self.no_auth, next_url=settings.LOCAL_ANGULAR_URL) + assert data.get('status_code') == http_status.HTTP_302_FOUND + assert data.get('next_url') == cas.get_login_url(request.url) + def test_next_url_login_without_auth(self): # login: user without auth request.url = web_url_for('auth_login', next=self.next_url, _absolute=True) @@ -827,6 +838,22 @@ def test_logout_with_no_parameter(self): assert resp.status_code == http_status.HTTP_302_FOUND assert cas.get_logout_url(self.goodbye_url) == resp.headers['Location'] + @mock.patch('framework.auth.views.settings.LOCAL_ANGULAR_URL', 'http://localhost:4200') + def test_logout_with_angular_next_url_logged_in(self): + angular_url = 'http://localhost:4200/' + logout_url = web_url_for('auth_logout', _absolute=True, next=angular_url) + resp = self.app.get(logout_url, auth=self.auth_user.auth) + assert resp.status_code == http_status.HTTP_302_FOUND + assert cas.get_logout_url(logout_url) == resp.headers['Location'] + + @mock.patch('framework.auth.views.settings.LOCAL_ANGULAR_URL', 'http://localhost:4200') + def test_logout_with_angular_next_url_logged_out(self): + angular_url = 'http://localhost:4200/' + logout_url = web_url_for('auth_logout', _absolute=True, next=angular_url) + resp = self.app.get(logout_url, auth=None) + assert resp.status_code == http_status.HTTP_302_FOUND + assert angular_url == resp.headers['Location'] + class TestResetPassword(OsfTestCase): diff --git a/website/settings/defaults.py b/website/settings/defaults.py index fbe9b939ae1..75edc0bd5a6 100644 --- a/website/settings/defaults.py +++ b/website/settings/defaults.py @@ -90,6 +90,7 @@ def parent_dir(path): INTERNAL_DOMAIN = DOMAIN API_DOMAIN = PROTOCOL + 'localhost:8000/' RESET_PASSWORD_URL = PROTOCOL + 'localhost:5000/resetpassword/' # TODO set angular reset password url +LOCAL_ANGULAR_URL = 'localhost:4200' PREPRINT_PROVIDER_DOMAINS = { 'enabled': False,