diff --git a/gregsite/settings/testing.py b/gregsite/settings/testing.py new file mode 100644 index 0000000000000000000000000000000000000000..fa99b44bf7f7f39459069d64930c32521a7ae72d --- /dev/null +++ b/gregsite/settings/testing.py @@ -0,0 +1,45 @@ +from .base import * + +AUTHENTICATION_BACKENDS = [ + "gregui.authentication.auth_backends.DevBackend", # Fake dev backend + "django.contrib.auth.backends.ModelBackend", # default + "gregui.authentication.auth_backends.GregOIDCBackend", + "sesame.backends.ModelBackend", # link login +] + +OIDC_RP_CLIENT_ID = 'lalalalala' +OIDC_RP_CLIENT_SECRET = 'lalalalala' + +LOGIN_REDIRECT_URL = "http://localhost:3000/" +LOGOUT_REDIRECT_URL = "http://localhost:3000/" + +CSRF_COOKIE_SAMESITE = "Strict" +SESSION_COOKIE_SAMESITE = "Lax" +# CSRF_COOKIE_HTTPONLY = True +# SESSION_COOKIE_HTTPONLY = True + + +ALLOWED_HOSTS += ["localhost", "127.0.0.1"] +# EMAIL_HOST = "smtp.uio.no" +# EMAIL_PORT = "468" +# EMAIL_USE_SSL = True +# EMAIL_TIMEOUT = 2 +# DEFAULT_FROM_EMAIL = "noreply@uio.no" +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +ORGREG_CLIENT = { + "endpoints": {"base_url": "https://example.com/fake/"}, + "headers": {"X-Gravitee-Api-Key": "bar"}, +} + +Q_CLUSTER = { + "name": "greg", + "workers": 4, + "timeout": 90, + "retry": 120, + "queue_limit": 50, + "bulk": 10, + "orm": "default", + "sync": True, +} + diff --git a/gregui/tests/conftest.py b/gregui/tests/conftest.py index 2213168a9c3820fa8cf3138ac89d10b52d100070..8f98331f2f0882c010cd3d469cfbcfe78251e1df 100644 --- a/gregui/tests/conftest.py +++ b/gregui/tests/conftest.py @@ -1,6 +1,7 @@ import datetime -import logging +import time +import logging import pytest @@ -19,12 +20,107 @@ from greg.models import ( RoleType, Sponsor, ) + from gregui.models import GregUserProfile # faker spams the logs with localisation warnings # see https://github.com/joke2k/faker/issues/753 logging.getLogger("faker").setLevel(logging.ERROR) +# OIDC stuff +@pytest.fixture +def claims(): + return { + "sub": "subsub", + "connect-userid_sec": ["feide:frank_foreleser@spusers.feide.no"], + "dataporten-userid_sec": [ + # "feide:frank_foreleser@spusers.feide.no" + ], + "name": "Frank Foreleser Føllesen", + "email": "noreply@feide.no", + "email_verified": True, + "picture": "https://api.dataporten.no/userinfo/v1/user/media/p:2192dff7-6989-4244-83cc-ae5e78875bdd", + } + + +@pytest.fixture +def id_token_payload(): + return { + "iss": "https://auth.dataporten.no", + "jti": "jtijti", + "aud": "lalalalala", + "sub": "subsub", + "iat": 1605174731, + "exp": 1605178331, + "auth_time": 1605174731, + "nonce": "noncenonce", + } + +@pytest.fixture +def data(): + return { + "User": { + "user1": { + "username": "user1", + "email": "user1@example.com", + }, + "user2": { + "username": "user2", + "email": "user2@example.com", + }, + "https://auth.dataporten.nosubsub": { + "username": "https://auth.dataporten.nosubsub", + }, + }, + "Identity": { + "person1": { + "type": "feide_id", + "value": "foo@example.com", + "person": { "email": "foo@example.com" } + }, + }, + "Person": { + "person1": { + "first_name": "Foo", + "last_name": "Baresen", + "email": "foo@example.com", + }, + }, + "Sponsor": { + "sponsor1": { + "first_name": "Bar", + "last_name": "Bazesen", + "feide_id": "bar@example.com", + }, + }, + } + + +def save_object(model, **kwargs): + obj = model(**kwargs) + obj.save() + return obj + +# TODO add person and sponsor +OBJECT_MAPPING = { + "greg" + "person": Person, + "sponsor": Sponsor, + "user": get_user_model(), +} + + +def create_objects(cls, data): + objects = {} + for obj_name, kwargs in data.items(): + create_kwargs = kwargs.copy() + for name, selector in kwargs.items(): + if name in OBJECT_MAPPING: + create_kwargs[name] = OBJECT_MAPPING[name].objects.get(**selector) + obj = save_object(cls, **create_kwargs) + objects[obj_name] = obj + return objects + @pytest.fixture def client() -> APIClient: @@ -133,3 +229,32 @@ def invitation_link_expired(invitation, invitation_expired_date) -> InvitationLi invitation=invitation, expire=invitation_expired_date ) return InvitationLink.objects.get(id=il.id) + + +def greg_users(data): + return create_objects(get_user_model(), data["User"]) + + +@pytest.fixture +def greg_persons(data): + return create_objects(get_user_model(), data["Person"]) + + +@pytest.fixture +def greg_sponsors(data): + return create_objects(get_user_model(), data["Sponsor"]) + + +@pytest.fixture +def log_in(client, greg_users): + def _log_in(username): + user = greg_users[username] + client.force_login(user=user) + # It seems like the session was not updated automatically this way + session = client.session + session["oidc_id_token_payload"] = {"iat": time.time()} + session.save() + return client + + return _log_in + diff --git a/gregui/tests/test_oidc.py b/gregui/tests/test_oidc.py new file mode 100644 index 0000000000000000000000000000000000000000..94e755916f6efb4b43b6cb62d74efcfa5521fd89 --- /dev/null +++ b/gregui/tests/test_oidc.py @@ -0,0 +1,76 @@ +import time +import pytest + +from django.core.exceptions import SuspiciousOperation +from django.conf import settings + +from greg.models import Identity, Person, Sponsor +from gregui.authentication.auth_backends import GregOIDCBackend +from gregui.models import GregUserProfile + + +pytestmark = pytest.mark.django_db + + +def test_validate_issuer(id_token_payload): + backend = GregOIDCBackend() + backend.validate_issuer(id_token_payload) + id_token_payload["iss"] = "http://suspicious.no" + with pytest.raises(SuspiciousOperation): + backend.validate_issuer(id_token_payload) + + +def test_validate_audiences(id_token_payload): + backend = GregOIDCBackend() + backend.validate_audience(id_token_payload) + + id_token_payload["aud"] = [id_token_payload["aud"], "other_aud"] + with pytest.raises(SuspiciousOperation): + backend.validate_audience(id_token_payload) + + +def test_validate_expiry(id_token_payload): + backend = GregOIDCBackend() + with pytest.raises(SuspiciousOperation): + backend.validate_expiry(id_token_payload) + + id_token_payload["exp"] = int(time.time()) + 3600 + backend.validate_expiry(id_token_payload) + + +def test_filter_users(greg_users, claims): + backend = GregOIDCBackend() + user = backend.filter_users_by_claims(claims).get() + assert user.username == greg_users["https://auth.dataporten.nosubsub"].username + + claims["sub"] = "non-existant-sub" + users = backend.filter_users_by_claims(claims) + assert len(users) == 0 + + +def test_create_user(claims): + backend = GregOIDCBackend() + user = backend.create_user(claims) + assert user.first_name == "Frank Foreleser" + assert user.last_name == "Føllesen" + assert user.email == "noreply@feide.no" + + userProfile = GregUserProfile.objects.get(user=user) + assert userProfile + + person = userProfile.person + assert person.first_name == user.first_name + assert person.last_name == user.last_name + assert person.email == user.email + + ids = Identity.objects.get(person=person, type='feide_id') + assert ids.value == 'frank_foreleser@spusers.feide.no' + + +def test_update_user(greg_users, claims): + backend = GregOIDCBackend() + user = backend.update_user(None, claims) + + assert user.first_name == "Frank Foreleser" + assert user.last_name == "Føllesen" + assert user.email == "noreply@feide.no" diff --git a/pytest.ini b/pytest.ini index a50d688a117f4b733118807805da3de16ce11ce5..a104a9933a8c2350e13ae25c47fa8c53862643f8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -DJANGO_SETTINGS_MODULE = gregsite.settings.dev +DJANGO_SETTINGS_MODULE = gregsite.settings.testing