[mailman3] Add patch for django_mailman3

Till https://src.fedoraproject.org/rpms/python-django-mailman3/pull-request/2 is
merged let's apply the patch directly.

Signed-off-by: Michal Konecny <mkonecny@redhat.com>
This commit is contained in:
Michal Konecny 2024-06-21 10:05:45 +02:00 committed by zlopez
parent 4f35e96a31
commit d0e06d1ab0
2 changed files with 430 additions and 0 deletions

View file

@ -0,0 +1,422 @@
diff --git a/django_mailman3/lib/auth/fedora/provider.py b/django_mailman3/lib/auth/fedora/provider.py
index b371fbb..966dda0 100644
--- a/django_mailman3/lib/auth/fedora/provider.py
+++ b/django_mailman3/lib/auth/fedora/provider.py
@@ -18,59 +18,71 @@
#
# Author: Aurelien Bompard <abompard@fedoraproject.org>
#
+import logging
-from urllib.parse import urlparse
-
-from django.urls import reverse
-from django.utils.http import urlencode
+from django.conf import settings as django_settings
from allauth.account.models import EmailAddress
from allauth.socialaccount import providers
-from allauth.socialaccount.providers.openid.provider import (
- OpenIDAccount, OpenIDProvider)
-from allauth.socialaccount.providers.openid.utils import (
- get_email_from_response)
+from allauth.socialaccount.providers.base import ProviderAccount
+from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
-def extract_username(url):
- return urlparse(url).netloc.split('.')[0]
+_log = logging.getLogger(__name__)
-class FedoraAccount(OpenIDAccount):
+class FedoraAccount(ProviderAccount):
def get_brand(self):
return dict(id='fedora', name='Fedora')
def to_str(self):
- return extract_username(self.account.uid)
+ return self.account.extra_data.get("preferred_username")
-class FedoraProvider(OpenIDProvider):
+class FedoraProvider(OAuth2Provider):
id = 'fedora'
name = 'Fedora'
account_class = FedoraAccount
- endpoint = 'https://id.fedoraproject.org'
- login_view = 'fedora_login'
- def get_login_url(self, request, **kwargs):
- url = reverse(self.login_view)
- if kwargs:
- url += '?' + urlencode(kwargs)
+ @property
+ def settings(self):
+ if not hasattr(self, "_settings"):
+ self._settings = django_settings.SOCIALACCOUNT_PROVIDERS.get(
+ self.id
+ )
+ return self._settings
+
+ @property
+ def server_url(self):
+ url = self.settings["server_url"]
+ return self.wk_server_url(url)
+
+ def wk_server_url(self, url):
+ well_known_uri = "/.well-known/openid-configuration"
+ if not url.endswith(well_known_uri):
+ url += well_known_uri
return url
- def extract_username(self, data):
- """
- https://fedoraproject.org/wiki/OpenID
- For fedoraproject.org, the identity_url looks like:
+ @property
+ def token_auth_method(self):
+ return self.settings.get("token_auth_method")
- https://username.id.fedoraproject.org
- """
- return extract_username(data.identity_url)
+ def get_default_scope(self):
+ return ["openid", "profile", "email"]
+
+ def extract_uid(self, data):
+ return str(data["sub"])
def extract_common_fields(self, data):
- fields = super(FedoraProvider, self).extract_common_fields(data)
- fields['username'] = self.extract_username(data)
- return fields
+ return dict(
+ email=data.get("email"),
+ username=data.get("preferred_username"),
+ name=data.get("name"),
+ user_id=data.get("user_id"),
+ picture=data.get("picture"),
+ zoneinfo=data.get("zoneinfo"),
+ )
def extract_email_addresses(self, data):
"""
@@ -81,7 +93,7 @@ class FedoraProvider(OpenIDProvider):
primary=True)]
"""
ret = []
- primary_email = get_email_from_response(data)
+ primary_email = data.get("email")
if primary_email:
# It would be added by cleanup_email_addresses(), but we add it
# here to mark it as verified.
@@ -89,7 +101,7 @@ class FedoraProvider(OpenIDProvider):
email=primary_email, verified=True, primary=True))
# Add the email alias provided by the Fedora project.
ret.append(EmailAddress(
- email='%s@fedoraproject.org' % self.extract_username(data),
+ email='%s@fedoraproject.org' % self.extract_uid(data),
verified=True, primary=False))
return ret
diff --git a/django_mailman3/lib/auth/fedora/urls.py b/django_mailman3/lib/auth/fedora/urls.py
index ca371a8..54dd656 100644
--- a/django_mailman3/lib/auth/fedora/urls.py
+++ b/django_mailman3/lib/auth/fedora/urls.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# Copyright (C) 2012-2023 by the Free Software Foundation, Inc.
+# Copyright (C) 2012-2024 by the Free Software Foundation, Inc.
#
# This file is part of Django-Mailman.
#
@@ -18,16 +18,9 @@
#
# Author: Aurelien Bompard <abompard@fedoraproject.org>
#
+from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
+from .provider import FedoraProvider
-from django.urls import re_path
-from . import views
-
-
-urlpatterns = [
- re_path('^fedora/login/$', views.LoginView.as_view(),
- name="fedora_login"),
- re_path('^fedora/callback/$', views.CallbackView.as_view(),
- name='fedora_callback'),
-]
+urlpatterns = default_urlpatterns(FedoraProvider)
diff --git a/django_mailman3/lib/auth/fedora/views.py b/django_mailman3/lib/auth/fedora/views.py
index 505f9f7..4b1779e 100644
--- a/django_mailman3/lib/auth/fedora/views.py
+++ b/django_mailman3/lib/auth/fedora/views.py
@@ -18,107 +18,60 @@
#
# Author: Aurelien Bompard <abompard@fedoraproject.org>
#
-
-from django.http import HttpResponseRedirect
-from django.shortcuts import render
-from django.urls import reverse
-from django.utils.decorators import method_decorator
-from django.views.decorators.csrf import csrf_exempt
-from django.views.generic import View
-
-from allauth.socialaccount import providers
-from allauth.socialaccount.app_settings import QUERY_EMAIL
-from allauth.socialaccount.helpers import (
- complete_social_login, render_authentication_error)
-from allauth.socialaccount.models import SocialLogin
-from allauth.socialaccount.providers.base import AuthError
-from allauth.socialaccount.providers.openid.forms import LoginForm
-from allauth.socialaccount.providers.openid.utils import (
- AXAttributes, SRegFields)
-from allauth.socialaccount.providers.openid.views import _openid_consumer
-from openid.consumer import consumer
-from openid.consumer.discover import DiscoveryFailure
-from openid.extensions.ax import AttrInfo, FetchRequest
-from openid.extensions.sreg import SRegRequest
+import requests
+from allauth.socialaccount.providers.oauth2.views import (
+ OAuth2Adapter, OAuth2CallbackView, OAuth2LoginView)
from .provider import FedoraProvider
-class LoginView(View):
+class FedoraAdapter(OAuth2Adapter):
+ provider_id = FedoraProvider.id
+
+ @property
+ def openid_config(self):
+ if not hasattr(self, "_openid_config"):
+ server_url = self.get_provider().server_url
+ resp = requests.get(server_url)
+ resp.raise_for_status()
+ self._openid_config = resp.json()
+ return self._openid_config
- form_class = LoginForm
- template_name = 'openid/login.html'
- provider = FedoraProvider
- callback_view = 'fedora_callback'
+ @property
+ def basic_auth(self):
+ token_auth_method = self.get_provider().settings.get(
+ "token_auth_method"
+ )
+ if token_auth_method:
+ return token_auth_method == "client_secret_basic"
+ return "client_secret_basic" in self.openid_config.get(
+ "token_endpoint_auth_methods_supported", []
+ )
- def get(self, request, *args, **kwargs):
- if 'openid' in request.GET or self.provider.endpoint:
- return self.post(request, *args, **kwargs)
- form = LoginForm(initial={'next': request.GET.get('next'),
- 'process': request.GET.get('process')})
- return render(request, self.template_name, {'form': form})
+ @property
+ def access_token_url(self):
+ return self.openid_config["token_endpoint"]
- def post(self, request, *args, **kwargs):
- data = dict(list(request.GET.items()) + list(request.POST.items()))
- if self.provider.endpoint:
- data['openid'] = self.provider.endpoint
- form = LoginForm(data)
- if form.is_valid():
- client = _openid_consumer(
- request, self.provider, self.provider.endpoint)
- try:
- auth_request = client.begin(form.cleaned_data['openid'])
- if QUERY_EMAIL:
- sreg = SRegRequest()
- for name in SRegFields:
- sreg.requestField(field_name=name,
- required=True)
- auth_request.addExtension(sreg)
- ax = FetchRequest()
- for name in AXAttributes:
- ax.add(AttrInfo(name,
- required=True))
- auth_request.addExtension(ax)
- callback_url = reverse(self.callback_view)
- SocialLogin.stash_state(request)
- redirect_url = auth_request.redirectURL(
- request.build_absolute_uri('/'),
- request.build_absolute_uri(callback_url))
- return HttpResponseRedirect(redirect_url)
- # UnicodeDecodeError:
- # see https://github.com/necaris/python3-openid/issues/1
- except (UnicodeDecodeError, DiscoveryFailure) as e:
- if request.method == 'POST':
- form._errors["openid"] = form.error_class([e])
- else:
- return render_authentication_error(
- request, self.provider.id, exception=e)
- return render(request, self.template_name, {'form': form})
+ @property
+ def authorize_url(self):
+ return self.openid_config["authorization_endpoint"]
+ @property
+ def profile_url(self):
+ return self.openid_config["userinfo_endpoint"]
-class CallbackView(View):
+ def complete_login(self, request, app, token, response):
+ response = (
+ requests.get(self.profile_url, headers={
+ "Authorization": "Bearer " + str(token)
+ })
+ )
+ response.raise_for_status()
+ extra_data = response.json()
+ return self.get_provider().sociallogin_from_response(
+ request, extra_data
+ )
- provider = FedoraProvider
- @method_decorator(csrf_exempt)
- def dispatch(self, request, *args, **kwargs):
- client = _openid_consumer(request)
- response = client.complete(
- dict(list(request.GET.items()) + list(request.POST.items())),
- request.build_absolute_uri(request.path))
- if response.status == consumer.SUCCESS:
- login = providers.registry \
- .by_id(self.provider.id) \
- .sociallogin_from_response(request, response)
- login.state = SocialLogin.unstash_state(request)
- ret = complete_social_login(request, login)
- else:
- if response.status == consumer.CANCEL:
- error = AuthError.CANCELLED
- else:
- error = AuthError.UNKNOWN
- ret = render_authentication_error(
- request,
- self.provider.id,
- error=error)
- return ret
+oauth2_login = OAuth2LoginView.adapter_view(FedoraAdapter)
+oauth2_callback = OAuth2CallbackView.adapter_view(FedoraAdapter)
diff --git a/django_mailman3/tests/test_lib_auth_fedora_provider.py b/django_mailman3/tests/test_lib_auth_fedora_provider.py
index 29c5508..851dce4 100644
--- a/django_mailman3/tests/test_lib_auth_fedora_provider.py
+++ b/django_mailman3/tests/test_lib_auth_fedora_provider.py
@@ -15,14 +15,13 @@
#
# You should have received a copy of the GNU General Public License along with
# Django-Mailman3. If not, see <http://www.gnu.org/licenses/>.
+from unittest.mock import patch
-
-from unittest.mock import Mock, patch
-
-from django.test import RequestFactory, TestCase
+from django.test import RequestFactory
from django.urls import reverse
-from openid.consumer import consumer
+from allauth.socialaccount.tests import setup_app
+from allauth.tests import TestCase
from django_mailman3.lib.auth.fedora.provider import (
FedoraAccount, FedoraProvider)
@@ -45,39 +44,61 @@ class TestFedoraProvider(TestCase):
"""
Test FedoraProvider openid authentication.
"""
+ provider_id = FedoraProvider.id
- @patch('allauth.socialaccount.providers.openid.views._openid_consumer')
- def setUp(self, consumer_mock):
- self.factory = RequestFactory()
- client = Mock()
- complete = Mock()
- consumer_mock.return_value = client
- client.complete = complete
- self.complete_response = Mock()
- complete.return_value = self.complete_response
- self.complete_response.status = consumer.SUCCESS
- self.complete_response.identity_url = 'http://bob.id.fedoraproject.org'
+ def setUp(self):
+ self.app = setup_app(self.provider_id)
+ self.app.provider_id = self.provider_id
+ self.app.provider = "fedora"
+ self.app.save()
+ self.request = RequestFactory().get("/")
+ self.provider = self.app.get_provider(self.request)
def test_get_login_url(self):
- req = self.factory.get('/')
- login_url = FedoraProvider(req).get_login_url(req)
+ req = self.request
+ login_url = self.provider.get_login_url(req)
self.assertEqual(login_url, reverse('fedora_login'))
- login_url = FedoraProvider(req).get_login_url(req, query1='value1')
+ login_url = self.provider.get_login_url(req, query1='value1')
new_url = reverse('fedora_login') + '?query1=value1'
self.assertEqual(login_url, new_url)
- def test_extract_username(self):
- req = self.factory.get('/')
- username = FedoraProvider(req).extract_username(self.complete_response)
- self.assertEqual(username, 'bob')
-
def test_extract_email_addresses(self):
- with patch('django_mailman3.lib.auth.fedora.provider'
- '.get_email_from_response') as email_mock:
- email_mock.return_value = 'testuser@example.com'
- req = self.factory.get('/')
- emails = FedoraProvider(req).extract_email_addresses(
- self.complete_response)
+ emails = self.provider.extract_email_addresses(
+ {"email": "testuser@example.com", "sub": "bob"})
self.assertEqual(len(emails), 2)
self.assertEqual(sorted([x.email for x in emails]),
['bob@fedoraproject.org', 'testuser@example.com'])
+
+ def test_server_url(self):
+ mock_sp_settings = {
+ "server_url": "https://id.fedoraproject.org"
+ }
+ with patch(
+ "django_mailman3.lib.auth.fedora.provider.django_settings"
+ ) as mock_settings:
+ mock_settings.SOCIALACCOUNT_PROVIDERS.get.return_value = (
+ mock_sp_settings
+ )
+ server_url = self.provider.server_url
+
+ self.assertEqual(
+ server_url,
+ "https://id.fedoraproject.org/.well-known/openid-configuration"
+ )
+
+ def test_token_auth_method(self):
+ mock_sp_settings = {
+ "token_auth_method": "basic_auth_token"
+ }
+ with patch(
+ "django_mailman3.lib.auth.fedora.provider.django_settings"
+ ) as mock_settings:
+ mock_settings.SOCIALACCOUNT_PROVIDERS.get.return_value = (
+ mock_sp_settings
+ )
+ token_auth_method = self.provider.token_auth_method
+
+ self.assertEqual(
+ token_auth_method,
+ "basic_auth_token"
+ )

View file

@ -26,6 +26,14 @@
- packages
- mailman
# This is needed till https://src.fedoraproject.org/rpms/python-django-mailman3/pull-request/2
# is available
- name: Apply django_mailman3 patch
ansible.posix.patch:
src: django_mailman3_patch/django-mailman3-fedora-oidc.patch
basedir: /usr/lib/python3.9/site-packages/django_mailman3/
strip: 1
- name: Set the mailman conffile
ansible.builtin.template:
src: mailman.cfg.j2