import datetime as dt import json import os from calendar import timegm import httpretty import pytest from django.contrib.auth import get_user_model from django.contrib.sessions.middleware import SessionMiddleware from jwkest.jwk import KEYS, RSAKey from jwkest.jws import JWS from rest_framework.exceptions import AuthenticationFailed from rest_framework.test import APIRequestFactory from oidc_rp.conf import settings as oidc_rp_settings from oidc_rp.contrib.rest_framework.authentication import BearerTokenAuthentication from oidc_rp.models import OIDCUser from oidc_rp.signals import oidc_user_created FIXTURE_ROOT = os.path.join(os.path.dirname(__file__), 'fixtures') @pytest.mark.django_db class TestBearerTokenAuthentication: @pytest.fixture(autouse=True) def setup(self): httpretty.enable() self.key = RSAKey(kid='testkey').load(os.path.join(FIXTURE_ROOT, 'testkey.pem')) def jwks(_request, _uri, headers): # noqa: E306 ks = KEYS() ks.add(self.key.serialize()) return 200, headers, ks.dump_jwks() httpretty.register_uri( httpretty.GET, oidc_rp_settings.PROVIDER_JWKS_ENDPOINT, status=200, body=jwks) httpretty.register_uri( httpretty.POST, oidc_rp_settings.PROVIDER_TOKEN_ENDPOINT, body=json.dumps({ 'id_token': self.generate_jws(), 'access_token': 'accesstoken', 'refresh_token': 'refreshtoken', }), content_type='text/json') httpretty.register_uri( httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body=json.dumps({'sub': '1234', 'email': 'test@example.com', }), content_type='text/json') yield httpretty.disable() def generate_jws(self, **kwargs): return JWS(self.generate_jws_dict(**kwargs), jwk=self.key, alg='RS256').sign_compact() def generate_jws_dict(self, **kwargs): client_key = kwargs.get('client_key', oidc_rp_settings.CLIENT_ID) now_dt = dt.datetime.utcnow() expiration_dt = kwargs.get('expiration_dt', (now_dt + dt.timedelta(seconds=30))) issue_dt = kwargs.get('issue_dt', now_dt) nonce = kwargs.get('nonce', 'nonce') return { 'iss': kwargs.get('iss', oidc_rp_settings.PROVIDER_ENDPOINT), 'nonce': nonce, 'aud': kwargs.get('aud', client_key), 'azp': kwargs.get('azp', client_key), 'exp': timegm(expiration_dt.utctimetuple()), 'iat': timegm(issue_dt.utctimetuple()), 'nbf': timegm(kwargs.get('nbf', now_dt).utctimetuple()), 'sub': '1234', } def test_can_authenticate_a_new_user(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() user, _ = backend.authenticate(request) assert user.email == 'test@example.com' assert user.oidc_user.sub == '1234' def test_can_authenticate_an_existing_user(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() user = get_user_model().objects.create_user('test', 'test@example.com') OIDCUser.objects.create(user=user, sub='1234') user, _ = backend.authenticate(request) assert user.email == 'test@example.com' assert user.oidc_user.sub == '1234' def test_cannot_authenticate_a_user_if_no_auth_header_is_present(self): rf = APIRequestFactory() request = rf.get('/') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() assert backend.authenticate(request) is None def test_cannot_authenticate_a_user_if_the_auth_header_is_not_a_bearer_authentication(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='DummyAuth accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() assert backend.authenticate(request) is None def test_cannot_authenticate_a_user_if_the_auth_header_does_not_contain_the_access_token(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request) def test_cannot_authenticate_a_user_if_multiple_tokens_are_present_in_the_auth_header(self): rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer token1 token2') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request) def test_cannot_authenticate_a_user_if_the_userinfo_endpoint_raises_an_error(self): httpretty.register_uri( httpretty.GET, oidc_rp_settings.PROVIDER_USERINFO_ENDPOINT, body='Nop', status=401) rf = APIRequestFactory() request = rf.get('/', HTTP_AUTHORIZATION='Bearer badtoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() with pytest.raises(AuthenticationFailed): backend.authenticate(request) def test_oidc_user_created_signal_is_sent_during_new_user_authentication(self, rf): self.signal_was_called = False def handler(sender, request, oidc_user, **kwargs): self.request = request self.oidc_user = oidc_user self.signal_was_called = True oidc_user_created.connect(handler) request = rf.get('/', HTTP_AUTHORIZATION='Bearer accesstoken') SessionMiddleware().process_request(request) request.session.save() backend = BearerTokenAuthentication() user, _ = backend.authenticate(request) assert self.signal_was_called is True assert user.email == 'test@example.com' assert user.oidc_user.sub == '1234' oidc_user_created.disconnect(handler)