+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endif %}
+
+ {% if request.session.message %}
+
+ {{ request.session.message }}
+
+ {% endif %}
+
+
+ '.format(self.username, self._id)
diff --git a/osf_tests/test_loa.py b/osf_tests/test_loa.py
new file mode 100644
index 00000000000..64dc417a1a1
--- /dev/null
+++ b/osf_tests/test_loa.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+"""Tests for the LoA (Level of Assurance) model."""
+import pytest
+
+from osf.models.loa import LoA
+from osf_tests.factories import InstitutionFactory, AuthUserFactory
+
+pytestmark = pytest.mark.django_db
+
+
+class TestBaseManager:
+ """Tests for BaseManager.get_or_none()."""
+
+ def test_get_or_none_returns_object_when_exists(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=1, ial=1, is_mfa=False, modifier=modifier,
+ )
+ result = LoA.objects.get_or_none(institution_id=institution.id)
+ assert result is not None
+ assert result.pk == loa.pk
+
+ def test_get_or_none_returns_none_when_not_exists(self):
+ result = LoA.objects.get_or_none(institution_id=99999)
+ assert result is None
+
+
+class TestLoAModel:
+ """Tests for the LoA model fields and behaviour."""
+
+ def test_create_loa_with_all_fields(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=2, ial=2, is_mfa=True, modifier=modifier,
+ )
+ assert loa.pk is not None
+ assert loa.institution == institution
+ assert loa.aal == 2
+ assert loa.ial == 2
+ assert loa.is_mfa is True
+ assert loa.modifier == modifier
+
+ def test_create_loa_with_null_aal_ial(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=None, ial=None, is_mfa=False, modifier=modifier,
+ )
+ assert loa.aal is None
+ assert loa.ial is None
+
+ def test_create_loa_with_zero_values(self):
+ """aal=0 and ial=0 represent NULL choice."""
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=0, ial=0, is_mfa=False, modifier=modifier,
+ )
+ assert loa.aal == 0
+ assert loa.ial == 0
+
+ def test_is_mfa_defaults_to_false(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, modifier=modifier,
+ )
+ assert loa.is_mfa is False
+
+ def test_loa_timestamps(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=1, ial=1, modifier=modifier,
+ )
+ assert loa.created is not None
+ assert loa.modified is not None
+
+ def test_cascade_delete_on_institution(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ LoA.objects.create(
+ institution=institution, aal=1, ial=1, modifier=modifier,
+ )
+ institution_id = institution.id
+ institution.delete()
+ assert LoA.objects.filter(institution_id=institution_id).count() == 0
+
+ def test_cascade_delete_on_modifier(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ LoA.objects.create(
+ institution=institution, aal=1, ial=1, modifier=modifier,
+ )
+ modifier_pk = modifier.pk
+ modifier.delete()
+ assert LoA.objects.filter(modifier_id=modifier_pk).count() == 0
+
+ def test_unicode_representation(self):
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ loa = LoA.objects.create(
+ institution=institution, aal=2, ial=1, is_mfa=True, modifier=modifier,
+ )
+ expected = u'institution_{}:{}:{}:{}'.format(
+ institution._id, 2, 1, True,
+ )
+ # LoA defines __unicode__ (not __str__), so call it directly
+ assert loa.__unicode__() == expected
+
+ def test_init_pops_node_kwarg(self):
+ """__init__ should silently pop 'node' from kwargs."""
+ institution = InstitutionFactory()
+ modifier = AuthUserFactory()
+ # Should not raise
+ loa = LoA(
+ institution=institution, aal=1, ial=1, modifier=modifier, node='anything',
+ )
+ assert loa.aal == 1
diff --git a/tests/nii/test_profile_from_idp.py b/tests/nii/test_profile_from_idp.py
index 2ee10b81043..5c78714f025 100644
--- a/tests/nii/test_profile_from_idp.py
+++ b/tests/nii/test_profile_from_idp.py
@@ -90,7 +90,7 @@ def test_without_email(self, app, institution, url_auth_institution):
make_payload(institution, eppn, fullname, given_name, family_name)
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=tmp_eppn_username)
assert user
assert user.fullname == fullname
@@ -114,7 +114,7 @@ def test_with_email(self, app, institution, url_auth_institution):
make_payload(institution, eppn, fullname, given_name, family_name, email=email)
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=email)
assert user
assert user.fullname == fullname
@@ -151,7 +151,7 @@ def test_with_email_and_profile_attr(self, app, institution, url_auth_institutio
)
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=email)
assert user
assert user.fullname == fullname
@@ -203,7 +203,7 @@ def test_with_email_and_profile_attr_without_orgname(self, app, institution, url
)
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=email)
assert user
assert user.fullname == fullname
@@ -230,7 +230,7 @@ def test_with_blacklist_email(self, app, institution, url_auth_institution):
make_payload(institution, eppn, fullname, given_name, family_name, email=email)
)
- assert res.status_code == 204
+ assert res.status_code == 200
# email is ignored
from django.core.exceptions import ObjectDoesNotExist
@@ -260,7 +260,7 @@ def test_same_email_is_ignored(self, app, institution, url_auth_institution):
url_auth_institution,
make_payload(institution, eppn, fullname, given_name, family_name, email=email)
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=email)
assert user
assert user.have_email == True
@@ -272,7 +272,7 @@ def test_same_email_is_ignored(self, app, institution, url_auth_institution):
url_auth_institution,
make_payload(institution, eppn2, fullname, given_name, family_name, email=email)
)
- assert res.status_code == 204
+ assert res.status_code == 200
# same email is ignored
user2 = OSFUser.objects.get(username=tmp_eppn_username2)
@@ -315,7 +315,7 @@ def test_existing_fullname_isnot_changed(self, app, institution, url_auth_instit
)
# user.fullname is not changned
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.get(username=email)
assert user
assert user.fullname == fullname
diff --git a/tests/test_202201/api/institutions/test_authenticate.py b/tests/test_202201/api/institutions/test_authenticate.py
index c1d9b2444c3..9b38a873a7b 100644
--- a/tests/test_202201/api/institutions/test_authenticate.py
+++ b/tests/test_202201/api/institutions/test_authenticate.py
@@ -98,7 +98,7 @@ def test_authenticate_jaSurname_and_jaGivenName_are_valid(
jaGivenName=jagivenname, jaSurname=jasurname),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
@@ -113,7 +113,7 @@ def test_authenticate_jaGivenName_is_valid(
make_payload(institution, username, jaGivenName=jagivenname),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.given_name_ja == jagivenname
@@ -128,7 +128,7 @@ def test_authenticate_jaSurname_is_valid(
make_payload(institution, username, jaSurname=jasurname),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.family_name_ja == jasurname
@@ -143,7 +143,7 @@ def test_authenticate_jaMiddleNames_is_valid(
make_payload(institution, username, jaMiddleNames=middlename),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.middle_names_ja == middlename
@@ -158,7 +158,7 @@ def test_authenticate_givenname_is_valid(
make_payload(institution, username, given_name=given_name),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.given_name == given_name
@@ -173,7 +173,7 @@ def test_authenticate_familyname_is_valid(
make_payload(institution, username, family_name=family_name),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.family_name == family_name
@@ -188,7 +188,7 @@ def test_authenticate_middlename_is_valid(
make_payload(institution, username, middle_names=middle_names),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username=username).first()
assert user
assert user.middle_names == middle_names
@@ -207,7 +207,7 @@ def test_authenticate_jaOrganizationalUnitName_is_valid(
organizationName=organizationname),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username='tmp_eppn_' + username).first()
assert user
assert user.jobs[0]['department_ja'] == jaorganizationname
@@ -226,7 +226,7 @@ def test_authenticate_OrganizationalUnitName_is_valid(
organizationName=organizationname),
expect_errors=True
)
- assert res.status_code == 204
+ assert res.status_code == 200
user = OSFUser.objects.filter(username='tmp_eppn_' + username).first()
assert user
assert user.jobs[0]['department'] == organizationnameunit
diff --git a/tests/test_profile_utils_loa.py b/tests/test_profile_utils_loa.py
new file mode 100644
index 00000000000..d0d522b1783
--- /dev/null
+++ b/tests/test_profile_utils_loa.py
@@ -0,0 +1,240 @@
+# -*- coding: utf-8 -*-
+"""Tests for LoA/MFA-related fields in website.profile.utils.serialize_user().
+
+Covers:
+ - _aal / _ial badge classification
+ - mfa_url construction and content
+ - is_mfa flag based on LoA settings
+ - Behaviour when no idp_attr / no LoA record exists
+"""
+import mock
+import pytest
+
+from osf.models.loa import LoA
+from osf.models import UserExtendedData
+from osf_tests.factories import AuthUserFactory, InstitutionFactory
+from tests.base import OsfTestCase
+from website import settings
+from website.profile.utils import serialize_user
+
+pytestmark = pytest.mark.django_db
+
+
+def _make_user_with_idp_attr(institution=None, ial=None, aal=None, idp='https://idp.example.ac.jp'):
+ """Helper: create a user with idp_attr set on UserExtendedData."""
+ user = AuthUserFactory()
+ user.ial = ial
+ user.aal = aal
+ user.save()
+
+ if institution is None:
+ institution = InstitutionFactory()
+ user.affiliated_institutions.add(institution)
+
+ ext, _ = UserExtendedData.objects.get_or_create(user=user)
+ ext.set_idp_attr({
+ 'id': institution.id,
+ 'idp': idp,
+ 'eppn': user.username,
+ 'username': user.username,
+ 'fullname': user.fullname,
+ 'email': user.username,
+ })
+
+ return user, institution
+
+
+class TestSerializeUserAalBadge(OsfTestCase):
+ """Tests for _aal classification in serialize_user()."""
+
+ def test_aal_null_when_no_aal(self):
+ user, _ = _make_user_with_idp_attr(aal=None)
+ result = serialize_user(user)
+ assert result['_aal'] == 'NULL'
+
+ def test_aal_null_when_empty_string(self):
+ user, _ = _make_user_with_idp_attr(aal='')
+ result = serialize_user(user)
+ assert result['_aal'] == 'NULL'
+
+ def test_aal2_when_aal_contains_aal2_url(self):
+ user, _ = _make_user_with_idp_attr(
+ aal='https://www.gakunin.jp/profile/AAL2',
+ )
+ result = serialize_user(user)
+ assert result['_aal'] == 'AAL2'
+
+ def test_aal1_when_aal_contains_aal1_url(self):
+ user, _ = _make_user_with_idp_attr(
+ aal='https://www.gakunin.jp/profile/AAL1',
+ )
+ result = serialize_user(user)
+ assert result['_aal'] == 'AAL1'
+
+ def test_aal1_when_aal_is_other_value(self):
+ """Any non-AAL2 truthy value should classify as AAL1."""
+ user, _ = _make_user_with_idp_attr(
+ aal='urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
+ )
+ result = serialize_user(user)
+ assert result['_aal'] == 'AAL1'
+
+ def test_raw_aal_value_is_preserved(self):
+ aal_value = 'https://www.gakunin.jp/profile/AAL2'
+ user, _ = _make_user_with_idp_attr(aal=aal_value)
+ result = serialize_user(user)
+ assert result['aal'] == aal_value
+
+
+class TestSerializeUserIalBadge(OsfTestCase):
+ """Tests for _ial classification in serialize_user()."""
+
+ def test_ial1_when_no_ial(self):
+ """When ial is None or empty, _ial should be IAL1 (default)."""
+ user, _ = _make_user_with_idp_attr(ial=None)
+ result = serialize_user(user)
+ assert result['_ial'] == 'IAL1'
+
+ def test_ial2_when_ial_contains_ial2_url(self):
+ user, _ = _make_user_with_idp_attr(
+ ial='https://www.gakunin.jp/profile/IAL2',
+ )
+ result = serialize_user(user)
+ assert result['_ial'] == 'IAL2'
+
+ def test_ial1_when_ial_is_other_value(self):
+ """Values other than IAL2 are equivalent to IAL1."""
+ user, _ = _make_user_with_idp_attr(ial='some_other_ial_value')
+ result = serialize_user(user)
+ assert result['_ial'] == 'IAL1'
+
+ def test_raw_ial_value_is_preserved(self):
+ ial_value = 'https://www.gakunin.jp/profile/IAL2'
+ user, _ = _make_user_with_idp_attr(ial=ial_value)
+ result = serialize_user(user)
+ assert result['ial'] == ial_value
+
+
+class TestSerializeUserMfaUrl(OsfTestCase):
+ """Tests for mfa_url construction in serialize_user()."""
+
+ @mock.patch.object(settings, 'OSF_MFA_URL', 'https://mfa.example.com/ds')
+ @mock.patch.object(settings, 'CAS_SERVER_URL', 'https://cas.example.com')
+ def test_mfa_url_constructed_when_entity_id_present(self):
+ """When idp_attr has entity_id (idp), mfa_url should be constructed."""
+ user, institution = _make_user_with_idp_attr(
+ idp='https://idp.example.ac.jp',
+ )
+ result = serialize_user(user)
+ mfa_url = result['mfa_url']
+ assert mfa_url != ''
+ # Should contain CAS logout redirect pattern
+ assert 'cas.example.com/logout' in mfa_url
+ # Should contain mfa.example.com/ds in the service param
+ assert 'mfa.example.com' in mfa_url
+
+ @mock.patch.object(settings, 'OSF_MFA_URL', 'https://mfa.example.com/ds')
+ @mock.patch.object(settings, 'CAS_SERVER_URL', 'https://cas.example.com')
+ def test_mfa_url_contains_entity_id(self):
+ entity_id = 'https://idp.specific.ac.jp'
+ user, institution = _make_user_with_idp_attr(idp=entity_id)
+ result = serialize_user(user)
+ mfa_url = result['mfa_url']
+ # The entityID should be URL-encoded within the mfa_url
+ assert 'idp.specific.ac.jp' in mfa_url
+
+ @mock.patch.object(settings, 'OSF_MFA_URL', 'https://mfa.example.com/ds')
+ @mock.patch.object(settings, 'CAS_SERVER_URL', 'https://cas.example.com')
+ def test_mfa_url_contains_login_service_url(self):
+ user, institution = _make_user_with_idp_attr()
+ result = serialize_user(user)
+ mfa_url = result['mfa_url']
+ # The CAS login URL is nested inside multiple urlencode layers,
+ # so slashes and colons are percent-encoded repeatedly.
+ # Fully decode the URL and then check for the expected substring.
+ from urllib.parse import unquote
+ decoded = mfa_url
+ for _ in range(5):
+ decoded = unquote(decoded)
+ assert 'cas.example.com/login' in decoded
+
+ def test_mfa_url_empty_when_no_entity_id(self):
+ """When idp_attr has no 'idp' key, mfa_url should be empty."""
+ user = AuthUserFactory()
+ user.aal = None
+ user.ial = None
+ user.save()
+
+ # Set idp_attr without 'idp' key
+ ext, _ = UserExtendedData.objects.get_or_create(user=user)
+ ext.set_idp_attr({
+ 'id': None,
+ 'username': user.username,
+ })
+
+ result = serialize_user(user)
+ assert result['mfa_url'] == ''
+
+ def test_mfa_url_empty_when_no_idp_attr(self):
+ """When user has no UserExtendedData, mfa_url should be empty."""
+ user = AuthUserFactory()
+ user.aal = None
+ user.ial = None
+ user.save()
+
+ result = serialize_user(user)
+ assert result['mfa_url'] == ''
+
+
+class TestSerializeUserIsMfa(OsfTestCase):
+ """Tests for is_mfa flag based on LoA settings."""
+
+ def test_is_mfa_true_when_loa_has_mfa_enabled(self):
+ user, institution = _make_user_with_idp_attr()
+ modifier = AuthUserFactory()
+ LoA.objects.create(
+ institution=institution, aal=2, ial=0, is_mfa=True, modifier=modifier,
+ )
+ result = serialize_user(user)
+ assert result['is_mfa'] is True
+
+ def test_is_mfa_false_when_loa_has_mfa_disabled(self):
+ user, institution = _make_user_with_idp_attr()
+ modifier = AuthUserFactory()
+ LoA.objects.create(
+ institution=institution, aal=2, ial=0, is_mfa=False, modifier=modifier,
+ )
+ result = serialize_user(user)
+ assert result['is_mfa'] is False
+
+ def test_is_mfa_false_when_no_loa_record(self):
+ user, institution = _make_user_with_idp_attr()
+ # No LoA record created
+ result = serialize_user(user)
+ assert result['is_mfa'] is False
+
+ def test_is_mfa_false_when_no_institution_id_in_idp_attr(self):
+ """When idp_attr has id=None, LoA lookup returns None -> is_mfa=False."""
+ user = AuthUserFactory()
+ user.aal = None
+ user.ial = None
+ user.save()
+
+ ext, _ = UserExtendedData.objects.get_or_create(user=user)
+ ext.set_idp_attr({
+ 'id': None,
+ 'idp': 'https://idp.example.ac.jp',
+ })
+
+ result = serialize_user(user)
+ assert result['is_mfa'] is False
+
+
+class TestSerializeUserReturnedKeys(OsfTestCase):
+ """Verify that all LoA-related keys are present in the serialized output."""
+
+ def test_loa_keys_present(self):
+ user, _ = _make_user_with_idp_attr()
+ result = serialize_user(user)
+ for key in ('ial', 'aal', '_ial', '_aal', 'mfa_url', 'is_mfa'):
+ assert key in result, 'Missing key: {}'.format(key)
diff --git a/website/profile/utils.py b/website/profile/utils.py
index 2a2ebe2d464..57ca9ea09b2 100644
--- a/website/profile/utils.py
+++ b/website/profile/utils.py
@@ -3,13 +3,17 @@
from api.base import settings as api_settings
from website import settings
-from osf.models import Contributor, UserQuota
+from osf.models import Contributor, UserQuota, LoA
from addons.osfstorage.models import Region
from website.filters import profile_image_url
from osf.utils.permissions import READ
from osf.utils import workflows
from api.waffle.utils import storage_i18n_flag_active
-from website.util import quota
+from website.util import quota, web_url_for
+
+# @R2022-48
+import re
+from urllib.parse import urlencode
def get_profile_image_url(user, size=settings.PROFILE_IMAGE_MEDIUM):
@@ -31,6 +35,47 @@ def serialize_user(user, node=None, admin=False, full=False, is_profile=False, i
user = contrib.user
fullname = user.display_full_name(node=node)
idp_attrs = user.get_idp_attr()
+
+ # @R2022-48
+ if not user.aal:
+ _aal = 'NULL'
+ elif re.search(settings.OSF_AAL2_STR, str(user.aal)):
+ _aal = 'AAL2'
+ else:
+ _aal = 'AAL1'
+
+ # @R-2024-AUTH01 Values other than IAL2 are equivalent to IAL1.
+ if re.search(settings.OSF_IAL2_STR, str(user.ial)):
+ _ial = 'IAL2'
+ else:
+ _ial = 'IAL1'
+
+ # @R-2023-55
+ mfa_url = ''
+ entity_id = idp_attrs.get('idp')
+ if entity_id is not None:
+ profile_url = web_url_for('user_profile', _absolute=True)
+
+ login_url = settings.CAS_SERVER_URL + '/login?' + urlencode({
+ 'service': profile_url,
+ })
+
+ mfa_url_q = settings.OSF_MFA_URL + '?' + urlencode({
+ 'entityID': entity_id,
+ 'target': login_url,
+ })
+
+ # CAS logout → MFA の redirect
+ mfa_url = settings.CAS_SERVER_URL + '/logout?' + urlencode({
+ 'service': mfa_url_q,
+ })
+
+ loa = LoA.objects.get_or_none(institution_id=idp_attrs.get('id'))
+ if loa is not None:
+ is_mfa = loa.is_mfa
+ else:
+ is_mfa = False
+
ret = {
'id': str(user._id),
'primary_key': user.id,
@@ -40,6 +85,12 @@ def serialize_user(user, node=None, admin=False, full=False, is_profile=False, i
'shortname': fullname if len(fullname) < 50 else fullname[:23] + '...' + fullname[-23:],
'profile_image_url': user.profile_image_url(size=settings.PROFILE_IMAGE_MEDIUM),
'active': user.is_active,
+ 'ial': user.ial, # @R2022-48
+ 'aal': user.aal, # @R2022-48
+ '_ial': _ial, # @R2022-48
+ '_aal': _aal, # @R2022-48
+ 'mfa_url': mfa_url, # @R-2023-55
+ 'is_mfa': is_mfa, # @R-2023-55
'have_email': user.have_email,
'idp_email': idp_attrs.get('email'),
}
diff --git a/website/routes.py b/website/routes.py
index 798ca41a892..2ccb72782c7 100644
--- a/website/routes.py
+++ b/website/routes.py
@@ -175,9 +175,11 @@ def get_globals():
'webpack_asset': paths.webpack_asset,
'osf_url': settings.INTERNAL_DOMAIN,
'waterbutler_url': settings.WATERBUTLER_URL,
+ 'cas_server_url': settings.CAS_SERVER_URL, # R-2022-48
'login_url': cas.get_login_url(request_login_url),
'sign_up_url': util.web_url_for('auth_register', _absolute=True, next=request_login_url),
'reauth_url': util.web_url_for('auth_logout', redirect_url=request.url, reauth=True),
+ 'mfa_url': settings.CAS_SERVER_URL + '/logout?service=' + settings.OSF_MFA_URL, # R-2023-55
'profile_url': cas.get_profile_url(),
'enable_institutions': settings.ENABLE_INSTITUTIONS,
'keen': {
diff --git a/website/settings/defaults.py b/website/settings/defaults.py
index 30887f5cd77..5c58c4951c6 100644
--- a/website/settings/defaults.py
+++ b/website/settings/defaults.py
@@ -374,6 +374,7 @@ def parent_dir(path):
CAS_SERVER_URL = 'http://localhost:8080'
MFR_SERVER_URL = 'http://localhost:7778'
+OSF_MFA_URL = '' # R-2022-48
###### ARCHIVER ###########
ARCHIVE_PROVIDER = 'osfstorage'
@@ -2098,5 +2099,13 @@ class CeleryConfig:
}
BABEL_DEFAULT_LOCALE = 'ja'
+# Default values for IAL2 & AAL2 parameters(R-2023-55)
+# Default values for IAL1 & AAL1 parameters(R-2024-AUTH01)
+OSF_IAL2_STR = 'https://www\.gakunin\.jp/profile/IAL2'
+OSF_AAL1_STR = 'https://www\.gakunin\.jp/profile/AAL1'
+OSF_AAL2_STR = 'https://www\.gakunin\.jp/profile/AAL2'
+OSF_IAL2_VAR = 'https://www.gakunin.jp/profile/IAL2'
+OSF_AAL1_VAR = 'https://www.gakunin.jp/profile/AAL1'
+OSF_AAL2_VAR = 'https://www.gakunin.jp/profile/AAL2'
# Prefix of isMemberOf attribute for groups.
MAP_GATEWAY_ISMEMBEROF_PREFIX = 'https://sptest.cg.gakunin.jp/gr/'
diff --git a/website/static/css/pages/profile-page.css b/website/static/css/pages/profile-page.css
index b1ad2f52177..393f9cd1c5b 100644
--- a/website/static/css/pages/profile-page.css
+++ b/website/static/css/pages/profile-page.css
@@ -140,3 +140,18 @@ table.social-links {
table.social-links a {
word-wrap: break-word;
}
+
+/*
+@R2022-48
+*/
+.badge.AAL1,
+.badge.IAL1
+{
+ background-color:#3498db!important;
+}
+
+.badge.AAL2,
+.badge.IAL2
+{
+ background-color:#18bc9c!important;
+}
diff --git a/website/static/img/institutions/banners/orthros-logo.png b/website/static/img/institutions/banners/orthros-logo.png
new file mode 100644
index 00000000000..d177b1ad076
Binary files /dev/null and b/website/static/img/institutions/banners/orthros-logo.png differ
diff --git a/website/static/img/institutions/shields-rounded-corners/orthros-shield-rounded-corners.png b/website/static/img/institutions/shields-rounded-corners/orthros-shield-rounded-corners.png
new file mode 100644
index 00000000000..b978279c11a
Binary files /dev/null and b/website/static/img/institutions/shields-rounded-corners/orthros-shield-rounded-corners.png differ
diff --git a/website/static/img/institutions/shields/orthros-shield.png b/website/static/img/institutions/shields/orthros-shield.png
new file mode 100644
index 00000000000..79f309c4e92
Binary files /dev/null and b/website/static/img/institutions/shields/orthros-shield.png differ
diff --git a/website/templates/profile.mako b/website/templates/profile.mako
index 1f71e1f59de..2dedebcd0c8 100644
--- a/website/templates/profile.mako
+++ b/website/templates/profile.mako
@@ -1,4 +1,5 @@
<%inherit file="base.mako"/>
+
<%namespace name="render_nodes" file="util/render_nodes.mako" />
<%def name="title()">${profile["fullname"]}%def>
<%def name="resource()"><%
@@ -64,7 +65,24 @@
${profile['display_absolute_url']} |
% endif
+ % if user['is_profile']:
+
+ | ${_("IAL") | n} |
+ ${profile['_ial']}${profile['ial']} |
+
+
+ | ${_("AuthnContext-Class") | n} |
+ ${profile['_aal']}${profile['aal']} |
+
+ % if profile.get('is_mfa') and profile.get('_aal') != "AAL2" and profile.get('mfa_url'):
+
+ | |
+ ${_("Log in again using multi-factor authentication")} |
+
+ % endif
+ % endif
+ % if user['is_profile']:
${profile['activity_points'] or _("No")} ${ngettext('activity point', 'activity points', profile['activity_points'])}
${profile["number_projects"]} ${_("project")}${ngettext(' ', 's', profile["number_projects"])}
@@ -73,6 +91,7 @@
${_("Usage of storage")}
${profile['quota']['rate']}%, ${profile['quota']['used']} / ${profile['quota']['max']}[GB]
+ % endif
diff --git a/website/translations/en/LC_MESSAGES/messages.po b/website/translations/en/LC_MESSAGES/messages.po
index 7f2084820e1..bd5a26a1afc 100644
--- a/website/translations/en/LC_MESSAGES/messages.po
+++ b/website/translations/en/LC_MESSAGES/messages.po
@@ -4082,6 +4082,18 @@ msgstr ""
msgid "If you do not have an email address registered, please enter or add your email address in the \"Registered email address\" entry field first."
msgstr ""
+#: website/templates/profile.mako:70
+msgid "IAL"
+msgstr ""
+
+#: website/templates/profile.mako:74
+msgid "AuthnContext-Class"
+msgstr ""
+
+#: website/templates/profile.mako:80
+msgid "Log in again using multi-factor authentication"
+msgstr ""
+
#: website/templates/util/render_addon_widget.mako
msgid "Available workflows"
msgstr ""
diff --git a/website/translations/ja/LC_MESSAGES/messages.po b/website/translations/ja/LC_MESSAGES/messages.po
index fe3c9a10b93..9bdfd8cc8f4 100644
--- a/website/translations/ja/LC_MESSAGES/messages.po
+++ b/website/translations/ja/LC_MESSAGES/messages.po
@@ -4443,6 +4443,18 @@ msgstr "%(summaryTitle)sのフォークを作成"
msgid "Manage Contributors"
msgstr "メンバー管理"
+#: website/templates/profile.mako:70
+msgid "IAL"
+msgstr ""
+
+#: website/templates/profile.mako:74
+msgid "AuthnContext-Class"
+msgstr ""
+
+#: website/templates/profile.mako:80
+msgid "Log in again using multi-factor authentication"
+msgstr "多要素認証で再ログインする"
+
#~ msgid ""
#~ "Because you have not configured the "
#~ "{addon} add-on, your authentication will"
diff --git a/website/translations/messages.pot b/website/translations/messages.pot
index dd4b7bcd503..fea0d185005 100644
--- a/website/translations/messages.pot
+++ b/website/translations/messages.pot
@@ -4347,6 +4347,18 @@ msgstr ""
msgid "Manage Contributors"
msgstr ""
+#: website/templates/profile.mako:70
+msgid "IAL"
+msgstr ""
+
+#: website/templates/profile.mako:74
+msgid "AuthnContext-Class"
+msgstr ""
+
+#: website/templates/profile.mako:80
+msgid "Log in again using multi-factor authentication"
+msgstr ""
+
#: website/templates/util/render_addon_widget.mako
msgid "Available workflows"
msgstr ""