diff --git a/tapir/accounts/backends.py b/tapir/accounts/backends.py index 622e9f82..943c4715 100644 --- a/tapir/accounts/backends.py +++ b/tapir/accounts/backends.py @@ -2,16 +2,17 @@ from django.contrib.auth.backends import BaseBackend from django.conf import settings from keycloak import KeycloakOpenID, KeycloakAuthenticationError +from tapir.accounts.models import KeycloakUser +User = get_user_model() class KeycloakAuthorizationCredentialsBackend(BaseBackend): def get_user(self, user_id): - UserModel = get_user_model() - + try: - user = UserModel.objects.get(pk=user_id) - except UserModel.DoesNotExist: + user = User.objects.get(pk=user_id) + except User.DoesNotExist: return None # needs to validate token before returning user @@ -26,11 +27,21 @@ def authenticate(self, request, username=None, password=None): client_secret_key=config["CLIENT_SECRET_KEY"], ) try: - token = kk.token("demo", "demo") + token = kk.token(username, password) except KeycloakAuthenticationError: return None - remote_user = kk.introspect(token["access_token"]) - # User a another model, that uses remore_user fill/update this instead of AUTH_USER_MODEL - UserModel = get_user_model() - return UserModel.objects.last() + user, _ = User.objects.get_or_create( + username=remote_user['sub'], + defaults={ + 'first_name': remote_user.get('given_name', ''), + 'last_name': remote_user.get('family_name', '') + } + ) + keycloakuser, _ = KeycloakUser.objects.get_or_create( + user=user, + sub=remote_user["sub"], + defaults={}, + ) + return keycloakuser.user + \ No newline at end of file diff --git a/tapir/accounts/migrations/0003_auto_20221117_1841.py b/tapir/accounts/migrations/0003_auto_20221117_1841.py new file mode 100644 index 00000000..02e62755 --- /dev/null +++ b/tapir/accounts/migrations/0003_auto_20221117_1841.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.16 on 2022-11-17 17:41 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import ldapdb.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounts', '0002_initial'), + ] + + operations = [ + migrations.CreateModel( + name='KeycloakUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sub', models.CharField(max_length=255, unique=True)), + ('realm', models.CharField(max_length=255)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='keycloak_user', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddConstraint( + model_name='keycloakuser', + constraint=models.UniqueConstraint(fields=('sub', 'realm'), name='unique_sub_realm'), + ), + ] diff --git a/tapir/accounts/models.py b/tapir/accounts/models.py index 47a43010..1016bb88 100644 --- a/tapir/accounts/models.py +++ b/tapir/accounts/models.py @@ -9,6 +9,7 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser, UserManager from django.contrib.auth.tokens import default_token_generator +from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives from django.db import connections, router, models from django.template import loader @@ -383,3 +384,18 @@ def middleware(request): return response return middleware + + +class KeycloakUser(models.Model): + user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='keycloak_user') + + sub = models.CharField(max_length=255, unique=True) + realm = models.CharField(max_length=255) + + + class Meta: + constraints = [ + models.UniqueConstraint(fields=['sub', 'realm'], name='unique_sub_realm') + ] + + \ No newline at end of file diff --git a/tapir/accounts/tests/test_keycloak.py b/tapir/accounts/tests/test_keycloak.py index 25f8ce33..cd24ecc5 100644 --- a/tapir/accounts/tests/test_keycloak.py +++ b/tapir/accounts/tests/test_keycloak.py @@ -1,16 +1,17 @@ +from django.contrib.auth import authenticate, login from tapir.utils.tests_utils import TapirFactoryTestBase from tapir.accounts.tests.factories.factories import TapirUserFactory from django.test import RequestFactory -class KeyCloakAuthentication(TapirFactoryTestBase): - - def setUp(self): - # Every test needs access to the request factory. - self.factory = RequestFactory() +class KeycloakAuthentication(TapirFactoryTestBase): + + def test_backend_authenticates_user_against_keycloak_server(self): + request = RequestFactory().get('/') + auth_user = authenticate(request, username='demo@demo.com', password='demo') + self.assertIsNotNone(auth_user) + + def test_backend_sets_active_status(self): + self.fail('check for the is_active key value and update') - def test_bla(self): - from django.contrib.auth import authenticate, login - request = self.factory.get('/') - user = TapirUserFactory() - auth_user = authenticate(request, username='asd', password='asd') - login(request, auth_user) \ No newline at end of file + def test_backend_differentiates_between_diferent_realms(self): + self.fail("user from another realm cannot login") \ No newline at end of file