Skip to content

Commit

Permalink
Enable OIDC tests
Browse files Browse the repository at this point in the history
This also converts the existing tests into pytest tests and extends
test coverage of the custom authentication backend.
  • Loading branch information
replaceafill committed Sep 23, 2024
1 parent 074d2b2 commit f10a101
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 55 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ ignore_errors = true
[[tool.mypy.overrides]]
module = [
"tests.integration.test_integration",
"tests.storage_service.test_oidc",
]
ignore_errors = false

Expand Down
17 changes: 11 additions & 6 deletions storage_service/common/backends.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import json
from typing import Any
from typing import Dict

from administration import roles
from django.conf import settings
from django.contrib.auth.models import User
from django_cas_ng.backends import CASBackend
from josepy.jws import JWS
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
Expand All @@ -21,22 +24,24 @@ def configure_user(self, user):
class CustomOIDCBackend(OIDCAuthenticationBackend):
"""Provide OpenID Connect authentication."""

def get_userinfo(self, access_token, id_token, verified_id):
def get_userinfo(
self, access_token: str, id_token: str, verified_id: Dict[str, Any]
) -> Dict[str, Any]:
"""Extract user details from JSON web tokens.
It returns a dict of user details that will be applied directly to the
user model.
"""

def decode_token(token):
def decode_token(token: str) -> Any:
sig = JWS.from_compact(token.encode("utf-8"))
payload = sig.payload.decode("utf-8")
return json.loads(payload)

access_info = decode_token(access_token)
id_info = decode_token(id_token)

info = {}
info: Dict[str, Any] = {}

for oidc_attr, user_attr in settings.OIDC_ACCESS_ATTRIBUTE_MAP.items():
if oidc_attr in access_info:
Expand All @@ -48,18 +53,18 @@ def decode_token(token):

return info

def create_user(self, user_info):
def create_user(self, user_info: Dict[str, Any]) -> User:
user = super().create_user(user_info)
for attr, value in user_info.items():
setattr(user, attr, value)
self.set_user_role(user)
return user

def update_user(self, user, user_info):
def update_user(self, user: User, user_info: Dict[str, Any]) -> User:
self.set_user_role(user)
return user

def set_user_role(self, user):
def set_user_role(self, user: User) -> None:
# TODO: use user claims accessible via user's authentication tokens.
role = roles.promoted_role(roles.USER_ROLE_READER)
user.set_role(role)
143 changes: 94 additions & 49 deletions tests/storage_service/test_oidc.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,97 @@
import pytest
import pytest_django
from administration import roles
from common.backends import CustomOIDCBackend
from django.conf import settings
from django.test import TestCase
from django.test import override_settings


@pytest.mark.skipif(
not settings.OIDC_AUTHENTICATION, reason="tests will only pass if OIDC is enabled"
)
class TestOIDC(TestCase):
def test_create_user(self):
backend = CustomOIDCBackend()
user = backend.create_user(
{"email": "test@example.com", "first_name": "Test", "last_name": "User"}
)

user.refresh_from_db()
assert user.first_name == "Test"
assert user.last_name == "User"
assert user.email == "test@example.com"
assert user.username == "test@example.com"
assert user.get_role() == roles.USER_ROLE_MANAGER

@override_settings(DEFAULT_USER_ROLE=roles.USER_ROLE_REVIEWER)
def test_create_demoted_user(self):
"""The role given to a new user is based on ``DEFAULT_USER_ROLE``.
In this test, we're ensuring that new users are given the reviewer role
instead of the default "manager" role.
"""
backend = CustomOIDCBackend()
user = backend.create_user(
{"email": "test@example.com", "first_name": "Test", "last_name": "User"}
)

user.refresh_from_db()
assert user.get_role() == roles.USER_ROLE_REVIEWER

def test_get_userinfo(self):
# Encoded at https://www.jsonwebtoken.io/
# {"email": "test@example.com"}
id_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJqdGkiOiI1M2QyMzUzMy04NDk0LTQyZWQtYTJiZC03Mzc2MjNmMjUzZjciLCJpYXQiOjE1NzMwMzE4NDQsImV4cCI6MTU3MzAzNTQ0NH0.m3nHgvj_DyVJMcW5eyYuUss1Y0PNzJV2O3bX0b_DCmI"
# {"given_name": "Test", "family_name": "User"}
access_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImp0aSI6ImRhZjIwNTNiLWE4MTgtNDE1Yy1hM2Y1LTkxYWVhMTMxYjljZCIsImlhdCI6MTU3MzAzMTk3OSwiZXhwIjoxNTczMDM1NTc5fQ.cGcmt7d9IuKndvrqPpAH3Dvb3KyCOMqixUWgS7sg8r4"

backend = CustomOIDCBackend()
info = backend.get_userinfo(access_token, id_token, None)
assert info["email"] == "test@example.com"
assert info["first_name"] == "Test"
assert info["last_name"] == "User"
from django.contrib.auth.models import User


@pytest.fixture
def settings(
settings: pytest_django.fixtures.SettingsWrapper,
) -> pytest_django.fixtures.SettingsWrapper:
settings.OIDC_OP_TOKEN_ENDPOINT = "https://example.com/token"
settings.OIDC_OP_USER_ENDPOINT = "https://example.com/user"
settings.OIDC_RP_CLIENT_ID = "rp_client_id"
settings.OIDC_RP_CLIENT_SECRET = "rp_client_secret"
settings.OIDC_ACCESS_ATTRIBUTE_MAP = {
"given_name": "first_name",
"family_name": "last_name",
}
settings.OIDC_ID_ATTRIBUTE_MAP = {"email": "email"}
settings.OIDC_USERNAME_ALGO = lambda email: email

return settings


@pytest.mark.django_db
def test_create_user(settings: pytest_django.fixtures.SettingsWrapper) -> None:
backend = CustomOIDCBackend()

user = backend.create_user(
{"email": "test@example.com", "first_name": "Test", "last_name": "User"}
)

user.refresh_from_db()
assert user.first_name == "Test"
assert user.last_name == "User"
assert user.email == "test@example.com"
assert user.username == "test@example.com"
assert user.get_role() == roles.USER_ROLE_READER


@pytest.mark.django_db
def test_create_demoted_user(settings: pytest_django.fixtures.SettingsWrapper) -> None:
"""The role given to a new user is based on ``DEFAULT_USER_ROLE``.
In this test, we're ensuring that new users are given the reviewer role
instead of the default "manager" role.
"""
settings.DEFAULT_USER_ROLE = roles.USER_ROLE_REVIEWER
backend = CustomOIDCBackend()

user = backend.create_user(
{"email": "test@example.com", "first_name": "Test", "last_name": "User"}
)

user.refresh_from_db()
assert user.get_role() == roles.USER_ROLE_REVIEWER


@pytest.mark.django_db
def test_get_userinfo(settings: pytest_django.fixtures.SettingsWrapper) -> None:
# Encoded at https://www.jsonwebtoken.io/
# {"email": "test@example.com"}
id_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJqdGkiOiI1M2QyMzUzMy04NDk0LTQyZWQtYTJiZC03Mzc2MjNmMjUzZjciLCJpYXQiOjE1NzMwMzE4NDQsImV4cCI6MTU3MzAzNTQ0NH0.m3nHgvj_DyVJMcW5eyYuUss1Y0PNzJV2O3bX0b_DCmI"
# {"given_name": "Test", "family_name": "User"}
access_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImp0aSI6ImRhZjIwNTNiLWE4MTgtNDE1Yy1hM2Y1LTkxYWVhMTMxYjljZCIsImlhdCI6MTU3MzAzMTk3OSwiZXhwIjoxNTczMDM1NTc5fQ.cGcmt7d9IuKndvrqPpAH3Dvb3KyCOMqixUWgS7sg8r4"
backend = CustomOIDCBackend()

info = backend.get_userinfo(access_token, id_token, {})

assert info["email"] == "test@example.com"
assert info["first_name"] == "Test"
assert info["last_name"] == "User"


@pytest.mark.django_db
def test_update_user(settings: pytest_django.fixtures.SettingsWrapper) -> None:
"""The role given to a new user is based on ``DEFAULT_USER_ROLE``.
In this test, we're ensuring that updating a user promotes it to a new role.
"""

user = User.objects.create(
first_name="Foo", last_name="Bar", username="foobar", email="foobar@example.com"
)
# User has been given the DEFAULT_USER_ROLE on creation.
assert user.get_role() == roles.USER_ROLE_READER
backend = CustomOIDCBackend()

# Promote the role in the DEFAULT_USER_ROLE setting.
settings.DEFAULT_USER_ROLE = roles.USER_ROLE_ADMIN

backend.update_user(user, {})

user.refresh_from_db()
# User has been promoted to the new role on update.
assert user.get_role() == roles.USER_ROLE_ADMIN

0 comments on commit f10a101

Please sign in to comment.