+ {% 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 7b80d5c9509..e0448c942d4 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 b3de0fbefdd..e1d3cf4864c 100644
--- a/website/routes.py
+++ b/website/routes.py
@@ -173,9 +173,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 1ace7782bba..ba9c55d5c36 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'
@@ -2094,3 +2095,12 @@ class CeleryConfig:
'ja_jp': '日本語'
}
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'
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 6d84a486354..dff3eba901f 100644
--- a/website/translations/en/LC_MESSAGES/messages.po
+++ b/website/translations/en/LC_MESSAGES/messages.po
@@ -4080,4 +4080,16 @@ msgid "\"Full name\", \"Family name\", \"Given name\", \"Family name (EN)\", \"G
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 ""
\ No newline at end of file
+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 ""
diff --git a/website/translations/ja/LC_MESSAGES/messages.po b/website/translations/ja/LC_MESSAGES/messages.po
index f94fbbaa2dd..b20ec9c95ed 100644
--- a/website/translations/ja/LC_MESSAGES/messages.po
+++ b/website/translations/ja/LC_MESSAGES/messages.po
@@ -4439,6 +4439,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 b1d25f76719..08be18af240 100644
--- a/website/translations/messages.pot
+++ b/website/translations/messages.pot
@@ -4345,3 +4345,14 @@ 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 ""