From 9def8c31940e0a52acb019fa79ba3c2efcd8c55a Mon Sep 17 00:00:00 2001
From: Jean-Baptiste <bleme@pm.me>
Date: Wed, 21 Aug 2019 11:15:02 +0200
Subject: [PATCH] update: extensible webfinger

---
 djangoldp/endpoints/__init__.py  |   0
 djangoldp/endpoints/webfinger.py | 107 +++++++++++++++++++++++++++++++
 djangoldp/urls.py                |   3 +-
 djangoldp/views.py               |  24 +++++++
 4 files changed, 133 insertions(+), 1 deletion(-)
 create mode 100644 djangoldp/endpoints/__init__.py
 create mode 100644 djangoldp/endpoints/webfinger.py

diff --git a/djangoldp/endpoints/__init__.py b/djangoldp/endpoints/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/djangoldp/endpoints/webfinger.py b/djangoldp/endpoints/webfinger.py
new file mode 100644
index 00000000..7cbf1cc4
--- /dev/null
+++ b/djangoldp/endpoints/webfinger.py
@@ -0,0 +1,107 @@
+import re
+from importlib import import_module
+
+from django.conf import settings
+
+
+class WebFinger(object):
+    def response(self, response_dict, rel, acct):
+        return response_dict
+
+
+for package in settings.DJANGOLDP_PACKAGES:
+    try:
+        import_module('{}.endpoints.webfinger'.format(package))
+    except ModuleNotFoundError:
+        pass
+
+model_classes = {cls.__name__: cls for cls in WebFinger.__subclasses__()}
+
+ACCT_RE = re.compile(
+    r'(?:acct:)?(?P<userinfo>[\w.!#$%&\'*+-/=?^_`{|}~]+)@(?P<host>[\w.:-]+)')
+
+
+class Acct(object):
+    def __init__(self, acct):
+        m = ACCT_RE.match(acct)
+        if not m:
+            raise ValueError('invalid acct format')
+        (userinfo, host) = m.groups()
+        self.userinfo = userinfo
+        self.host = host
+
+
+class WebFingerEndpoint(object):
+    """
+    WebFinger endpoint
+    See https://tools.ietf.org/html/rfc7033
+    """
+
+    def __init__(self, request):
+        self.request = request
+        self.params = {}
+        self.acct = None
+
+        self._extract_params()
+
+    def _extract_params(self):
+        # Because in this endpoint we handle both GET
+        # and POST request.
+        query_dict = (self.request.POST if self.request.method == 'POST'
+                      else self.request.GET)
+
+        self.params['resource'] = query_dict.get('resource', None)
+        self.params['rel'] = query_dict.get('rel', '')
+
+    def validate_params(self):
+        """
+        A resource must be set.
+        """
+
+        if self.params['resource'] is None:
+            raise WebFingerError('invalid_request')
+
+        try:
+            self.acct = Acct(self.params['resource'])
+        except ValueError:
+            raise WebFingerError('invalid_acct_format')
+
+    def response(self):
+        """
+        This endpoint only reply to rel="http://openid.net/specs/connect/1.0/issuer"
+        :return: a dict representing the Json response
+        """
+
+        dict = {
+            'subject': self.params['resource'],
+            'links': []
+        }
+
+        for class_name in model_classes:
+            webfinger = class_name()
+            dict = webfinger.response(dict, self.params['rel'], self.acct)
+
+        return dict
+
+
+class WebFingerError(Exception):
+    _errors = {
+        'invalid_request': "The request provider parameter must contains an url or an email",
+        'invalid_acct_format': "Invalid acct format"
+    }
+
+    def __init__(self, error=None, dict=None):
+        if dict is None:
+            self.error = error
+            self.description = self._errors.get(error)
+        else:
+            self.error = dict['error']
+            self.description = dict['error_description']
+
+    def create_dict(self):
+        dic = {
+            'error': self.error,
+            'error_description': self.description,
+        }
+
+        return dic
diff --git a/djangoldp/urls.py b/djangoldp/urls.py
index 39b9e351..4fd8d7f4 100644
--- a/djangoldp/urls.py
+++ b/djangoldp/urls.py
@@ -5,7 +5,7 @@ from django.conf.urls import url, include
 
 from djangoldp.models import LDPSource, Model
 from djangoldp.permissions import LDPPermissions
-from djangoldp.views import LDPSourceViewSet
+from djangoldp.views import LDPSourceViewSet, WebFingerView
 
 
 def __clean_path(path):
@@ -19,6 +19,7 @@ def __clean_path(path):
 urlpatterns = [
     url(r'^sources/(?P<federation>\w+)/', LDPSourceViewSet.urls(model=LDPSource, fields=['federation', 'container'],
                                                                 permission_classes=[LDPPermissions], )),
+    url(r'^\.well-known/webfinger/?$', WebFingerView.as_view()),
 ]
 
 for package in settings.DJANGOLDP_PACKAGES:
diff --git a/djangoldp/views.py b/djangoldp/views.py
index 48210094..748751fc 100644
--- a/djangoldp/views.py
+++ b/djangoldp/views.py
@@ -4,8 +4,10 @@ from django.conf.urls import url, include
 from django.contrib.auth import get_user_model
 from django.core.exceptions import FieldDoesNotExist
 from django.core.urlresolvers import get_resolver
+from django.http import JsonResponse
 from django.shortcuts import get_object_or_404
 from django.utils.decorators import classonlymethod
+from django.views import View
 from pyld import jsonld
 from rest_framework import status
 from rest_framework.authentication import SessionAuthentication
@@ -14,6 +16,7 @@ from rest_framework.renderers import JSONRenderer
 from rest_framework.response import Response
 from rest_framework.viewsets import ModelViewSet
 
+from djangoldp.endpoints.webfinger import WebFingerEndpoint, WebFingerError
 from djangoldp.models import LDPSource, Model
 from djangoldp.permissions import LDPPermissions
 
@@ -278,3 +281,24 @@ class LDPSourceViewSet(LDPViewSet):
 
     def get_queryset(self, *args, **kwargs):
         return super().get_queryset(*args, **kwargs).filter(federation=self.kwargs['federation'])
+
+
+class WebFingerView(View):
+    endpoint_class = WebFingerEndpoint
+
+    def get(self, request, *args, **kwargs):
+        return self.on_request(request)
+
+    def on_request(self, request):
+        endpoint = self.endpoint_class(request)
+        try:
+            endpoint.validate_params()
+
+            return JsonResponse(endpoint.response())
+
+        except WebFingerError as error:
+            return JsonResponse(error.create_dict(), status=400)
+
+    def post(self, request, *args, **kwargs):
+        return self.on_request(request)
+
-- 
GitLab