Skip to content
Snippets Groups Projects
Commit b66dca11 authored by Frank Lange's avatar Frank Lange
Browse files

merge branch 'allauth' into 'dev'

parents ab7a557d a6089d3c
No related branches found
No related tags found
No related merge requests found
Pipeline #1656288 passed
SECRET_KEY=
DEBUG=True
DALIA_TRIPLESTORE_BASE_URL=http://fuseki:3030/
IAM4NFDI_CLIENT_ID=
IAM4NFDI_CLIENT_SECRET=
# Redirect URI should point to the frontend's /auth/callback/iam4nfdi page (absolute URL).
IAM4NFDI_REDIRECT_URI=
# Frontend page to redirect to when OIDC login flow fails (absolute URL).
IAM4NFDI_LOGIN_ERROR_PAGE=
......@@ -2,12 +2,19 @@ FROM python:3.11
WORKDIR /django
COPY manage.py requirements.txt /django/
COPY manage.py requirements.txt django-allauth.patch /django/
COPY project/ /django/project
RUN \
set -ex && \
pip install --no-cache-dir -r requirements.txt
pip install --no-cache-dir -r requirements.txt && \
pip uninstall -y django-allauth[socialaccount] && \
git clone https://codeberg.org/allauth/django-allauth.git --depth 1 --branch 65.6.0 && \
cd django-allauth && \
git apply ../django-allauth.patch && \
cd .. && \
pip install -e django-allauth[socialaccount]
# Don't care about the relational database at the moment.
RUN \
......
diff --git a/allauth/socialaccount/providers/oauth2/client.py b/allauth/socialaccount/providers/oauth2/client.py
index c0098df..9730260 100644
--- a/allauth/socialaccount/providers/oauth2/client.py
+++ b/allauth/socialaccount/providers/oauth2/client.py
@@ -1,3 +1,6 @@
+import os
+import urllib
+
import requests
from urllib.parse import parse_qsl
@@ -28,7 +31,7 @@ class OAuth2Client:
self.request = request
self.access_token_method = access_token_method
self.access_token_url = access_token_url
- self.callback_url = callback_url
+ self.callback_url = os.environ.get("IAM4NFDI_REDIRECT_URI")
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
self.scope_delimiter = scope_delimiter
@@ -56,7 +59,7 @@ class OAuth2Client:
"code": code,
}
if self.basic_auth:
- auth = requests.auth.HTTPBasicAuth(self.consumer_key, self.consumer_secret)
+ auth = requests.auth.HTTPBasicAuth(urllib.parse.quote_plus(self.consumer_key), self.consumer_secret)
else:
auth = None
data.update(
......@@ -4,3 +4,7 @@ from django.apps import AppConfig
class DaliaConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'project.dalia'
def ready(self):
# Implicitly connect signal handlers decorated with @receiver.
import project.dalia.signals # noqa
# Generated by Django 5.1.7 on 2025-03-27 19:31
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('dalia_id', models.UUIDField(default=uuid.uuid4, unique=True)),
('orcid', models.CharField(max_length=37)),
],
),
]
import uuid
from django.conf import settings
from django.db import models
# Create your models here.
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
dalia_id = models.UUIDField(default=uuid.uuid4, unique=True)
orcid = models.CharField(max_length=37) # "https://orcid.org/XXXX-XXXX-XXXX-XXXX"
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from project.dalia.models import UserProfile
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def initialize_user_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile(user=instance)
instance.userprofile.save()
......@@ -21,4 +21,5 @@ urlpatterns = [
path('v1/curation/suggest/target-groups', views.CurationSuggestTargetGroupsView.as_view(), name="curation_suggest_target_groups"),
path('v1/curation/suggest/media-types', views.CurationSuggestMediaTypesView.as_view(), name="curation_suggest_media_types"),
path('v1/curation/suggest/relation-types', views.CurationSuggestRelationTypesView.as_view(), name="curation_suggest_relation_types"),
path("v1/hello/", views.HelloAPIView.as_view()),
]
from uuid import UUID
from allauth.headless.contrib.rest_framework.authentication import XSessionTokenAuthentication
from django.http import HttpResponse, HttpResponseNotFound
from rest_framework import authentication, permissions
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
......@@ -182,3 +184,15 @@ class CurationSuggestRelationTypesView(APIView):
get_relation_types_suggestions(), many=True
)
return Response(serializer.data)
# Example view to demonstrate how to implement authentication checks
class HelloAPIView(APIView):
authentication_classes = [
authentication.SessionAuthentication,
XSessionTokenAuthentication,
]
permission_classes = [permissions.IsAuthenticated]
def post(self, request: Request):
return Response({"message": f"Hello {request.user.username}!"})
......@@ -43,6 +43,11 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.openid_connect',
'allauth.headless',
'rest_framework',
'project.dalia',
]
......@@ -55,6 +60,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
]
ROOT_URLCONF = 'project.urls'
......@@ -75,6 +81,10 @@ TEMPLATES = [
},
]
AUTHENTICATION_BACKENDS = [
'allauth.account.auth_backends.AuthenticationBackend',
]
WSGI_APPLICATION = 'project.wsgi.application'
......@@ -130,6 +140,40 @@ STATIC_URL = 'static/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# django-allauth settings
HEADLESS_ONLY = True
SOCIALACCOUNT_ONLY = True
ACCOUNT_EMAIL_VERIFICATION = "none" # required by SOCIALACCOUNT_ONLY = True
SOCIALACCOUNT_PROVIDERS = {}
IAM4NFDI_CLIENT_ID = os.environ.get("IAM4NFDI_CLIENT_ID", None)
IAM4NFDI_CLIENT_SECRET = os.environ.get("IAM4NFDI_CLIENT_SECRET", None)
IAM4NFDI_REDIRECT_URI = os.environ.get("IAM4NFDI_REDIRECT_URI", None)
IAM4NFDI_LOGIN_ERROR_PAGE = os.environ.get("IAM4NFDI_LOGIN_ERROR_PAGE", None)
if IAM4NFDI_CLIENT_ID and IAM4NFDI_CLIENT_SECRET and IAM4NFDI_REDIRECT_URI and IAM4NFDI_LOGIN_ERROR_PAGE:
HEADLESS_FRONTEND_URLS = {
"socialaccount_login_error": IAM4NFDI_LOGIN_ERROR_PAGE,
}
SOCIALACCOUNT_PROVIDERS["openid_connect"] = {
"APPS": [
{
"provider_id": "iam4nfdi",
"name": "IAM4NFDI Infrastructure Proxy",
"client_id": IAM4NFDI_CLIENT_ID,
"secret": IAM4NFDI_CLIENT_SECRET,
"settings": {
"server_url": "https://infraproxy.nfdi-aai.dfn.de/.well-known/openid-configuration",
"auth_params": {
# response_type can be "code", "id_token" or "token" depending on OIDC provider config.
# "response_type": "...",
"redirect_uri": IAM4NFDI_REDIRECT_URI,
},
},
},
],
}
# dalia app settings
DALIA_TRIPLESTORE_BASE_URL = os.environ.get("DALIA_TRIPLESTORE_BASE_URL", "http://fuseki:3030/")
......@@ -20,4 +20,6 @@ from project.dalia import urls as dalia_urls
urlpatterns = [
path('api/dalia/', include(dalia_urls)),
path("api/accounts/", include("allauth.urls")),
path("api/_allauth/", include("allauth.headless.urls")),
]
......@@ -4,3 +4,4 @@ djangorestframework-dataclasses==1.3.1
gunicorn==23.0.0
rdflib==7.1.3
python-dotenv==1.0.1
django-allauth[socialaccount]==65.6.0
from django.contrib.auth import get_user_model
from project.dalia.models import UserProfile
def test_initialize_user_profile_signal_initializes_user_profile_after_user_is_created(db):
user_model = get_user_model()
assert user_model.objects.count() == 0
user = user_model(username="test")
user.save()
assert user_model.objects.count() == 1
assert UserProfile.objects.count() == 1
assert user.userprofile.orcid == ""
assert user.userprofile.dalia_id
def test_initialize_user_profile_signal_does_not_change_user_profile_after_user_is_changed(db):
user_model = get_user_model()
user = user_model(username="test")
user.save()
assert user_model.objects.count() == 1
assert UserProfile.objects.count() == 1
orcid = user.userprofile.orcid
dalia_id = user.userprofile.dalia_id
user.first_name = "abc"
user.save()
assert user_model.objects.count() == 1
assert UserProfile.objects.count() == 1
assert user.userprofile.orcid == orcid
assert user.userprofile.dalia_id == dalia_id
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment