Browse Source

Start implementing webmention receiving :o the status page is ugly and there's no actual verification yet, but good start at least ;)

tags/v1.9.4
Danielle McLean 1 year ago
parent
commit
169f0687cb
Signed by: Danielle McLean <dani@00dani.me> GPG Key ID: 5A5D2D1AFF12EEC5

+ 1
- 0
lemoncurry/settings/base.py View File

@@ -89,6 +89,7 @@ INSTALLED_APPS = [
89 89
     'lemonauth',
90 90
     'micropub',
91 91
     'users',
92
+    'webmention',
92 93
     'wellknowns',
93 94
 ]
94 95
 

+ 1
- 0
lemoncurry/urls.py View File

@@ -41,6 +41,7 @@ urlpatterns = [
41 41
     url('^auth/', include('lemonauth.urls')),
42 42
     url('^micropub', include('micropub.urls')),
43 43
     url('^s/', include('shorturls.urls')),
44
+    url('^webmention', include('webmention.urls')),
44 45
 
45 46
     url(r'^django-rq/', include('django_rq.urls')),
46 47
     url(r'^sitemap\.xml$', sitemap.index, maps, name='sitemap'),

+ 1
- 0
webmention/__init__.py View File

@@ -0,0 +1 @@
1
+default_app_config = 'webmention.apps.WebmentionConfig'

+ 5
- 0
webmention/apps.py View File

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class WebmentionConfig(AppConfig):
5
+    name = 'webmention'

+ 39
- 0
webmention/migrations/0001_initial.py View File

@@ -0,0 +1,39 @@
1
+# -*- coding: utf-8 -*-
2
+# Generated by Django 1.11.10 on 2018-03-19 01:18
3
+from __future__ import unicode_literals
4
+
5
+from django.db import migrations, models
6
+import django.db.models.deletion
7
+import django.utils.timezone
8
+import model_utils.fields
9
+
10
+
11
+class Migration(migrations.Migration):
12
+
13
+    initial = True
14
+
15
+    dependencies = [
16
+        ('entries', '0011_auto_20171120_1108'),
17
+    ]
18
+
19
+    operations = [
20
+        migrations.CreateModel(
21
+            name='Webmention',
22
+            fields=[
23
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
24
+                ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
25
+                ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
26
+                ('source', models.CharField(max_length=255)),
27
+                ('target', models.CharField(max_length=255)),
28
+                ('state', models.CharField(choices=[('p', 'pending'), ('v', 'valid'), ('i', 'invalid'), ('d', 'deleted')], default='p', max_length=1)),
29
+                ('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mentions', to='entries.Entry')),
30
+            ],
31
+            options={
32
+                'default_related_name': 'mentions',
33
+            },
34
+        ),
35
+        migrations.AlterUniqueTogether(
36
+            name='webmention',
37
+            unique_together=set([('source', 'target')]),
38
+        ),
39
+    ]

+ 0
- 0
webmention/migrations/__init__.py View File


+ 31
- 0
webmention/models.py View File

@@ -0,0 +1,31 @@
1
+from django.db import models
2
+from entries.models import Entry
3
+from model_utils.models import TimeStampedModel
4
+
5
+
6
+class State:
7
+    PENDING = 'p'
8
+    VALID = 'v'
9
+    INVALID = 'i'
10
+    DELETED = 'd'
11
+    CHOICES = (
12
+        (PENDING, 'pending'),
13
+        (VALID, 'valid'),
14
+        (INVALID, 'invalid'),
15
+        (DELETED, 'deleted'),
16
+    )
17
+
18
+
19
+class Webmention(TimeStampedModel):
20
+    entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
21
+    source = models.CharField(max_length=255)
22
+    target = models.CharField(max_length=255)
23
+    state = models.CharField(
24
+        choices=State.CHOICES,
25
+        default=State.PENDING,
26
+        max_length=1
27
+    )
28
+
29
+    class Meta:
30
+        default_related_name = 'mentions'
31
+        unique_together = ('source', 'target')

+ 8
- 0
webmention/urls.py View File

@@ -0,0 +1,8 @@
1
+from django.conf.urls import url
2
+from . import views
3
+
4
+app_name = 'webmention'
5
+urlpatterns = (
6
+    url('^$', views.accept, name='accept'),
7
+    url('^s/(?P<id>\d+)$', views.status, name='status')
8
+)

+ 68
- 0
webmention/views.py View File

@@ -0,0 +1,68 @@
1
+from django.http import HttpResponse
2
+from django.urls import resolve, reverse, Resolver404
3
+from django.shortcuts import get_object_or_404
4
+from django.views.decorators.csrf import csrf_exempt
5
+from django.views.decorators.http import require_GET, require_POST
6
+from entries.models import Entry
7
+from lemoncurry.utils import bad_req
8
+from urllib.parse import urljoin, urlparse
9
+
10
+from .models import State, Webmention
11
+
12
+
13
+@csrf_exempt
14
+@require_POST
15
+def accept(request):
16
+    if 'source' not in request.POST:
17
+        return bad_req('missing source url')
18
+    source_url = request.POST['source']
19
+
20
+    if 'target' not in request.POST:
21
+        return bad_req('missing target url')
22
+    target_url = request.POST['target']
23
+
24
+    source = urlparse(source_url)
25
+    target = urlparse(target_url)
26
+    if source.scheme not in ('http', 'https'):
27
+        return bad_req('unsupported source scheme')
28
+    if target.scheme not in ('http', 'https'):
29
+        return bad_req('unsupported target scheme')
30
+    if target.netloc != request.site.domain:
31
+        return bad_req('target not on this site')
32
+    origin = 'https://' + target.netloc
33
+
34
+    try:
35
+        match = resolve(target.path)
36
+    except Resolver404:
37
+        return bad_req('target not found')
38
+
39
+    if match.view_name != 'entries:entry':
40
+        return bad_req('target does not accept webmentions')
41
+
42
+    try:
43
+        entry = Entry.objects.get(pk=match.kwargs['id'])
44
+    except Entry.DoesNotExist:
45
+        return bad_req('target not found')
46
+
47
+    try:
48
+        mention = Webmention.objects.get(source=source_url, target=target_url)
49
+    except Webmention.DoesNotExist:
50
+        mention = Webmention()
51
+        mention.source = source_url
52
+        mention.target = target_url
53
+
54
+    mention.entry = entry
55
+    mention.state = State.PENDING
56
+    mention.save()
57
+    status = reverse('webmention:status', kwargs={'id': mention.id})
58
+
59
+    res = HttpResponse(status=201)
60
+    res['Location'] = urljoin(origin, status)
61
+    return res
62
+
63
+
64
+@csrf_exempt
65
+@require_GET
66
+def status(request, id):
67
+    mention = get_object_or_404(Webmention.objects, pk=id)
68
+    return HttpResponse(mention.get_state_display())

Loading…
Cancel
Save