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
9 changed files with 154 additions and 0 deletions
  1. +1
    -0
      lemoncurry/settings/base.py
  2. +1
    -0
      lemoncurry/urls.py
  3. +1
    -0
      webmention/__init__.py
  4. +5
    -0
      webmention/apps.py
  5. +39
    -0
      webmention/migrations/0001_initial.py
  6. +0
    -0
      webmention/migrations/__init__.py
  7. +31
    -0
      webmention/models.py
  8. +8
    -0
      webmention/urls.py
  9. +68
    -0
      webmention/views.py

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

@@ -89,6 +89,7 @@ INSTALLED_APPS = [
'lemonauth',
'micropub',
'users',
'webmention',
'wellknowns',
]


+ 1
- 0
lemoncurry/urls.py View File

@@ -41,6 +41,7 @@ urlpatterns = [
url('^auth/', include('lemonauth.urls')),
url('^micropub', include('micropub.urls')),
url('^s/', include('shorturls.urls')),
url('^webmention', include('webmention.urls')),

url(r'^django-rq/', include('django_rq.urls')),
url(r'^sitemap\.xml$', sitemap.index, maps, name='sitemap'),

+ 1
- 0
webmention/__init__.py View File

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

+ 5
- 0
webmention/apps.py View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig


class WebmentionConfig(AppConfig):
name = 'webmention'

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

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.10 on 2018-03-19 01:18
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
('entries', '0011_auto_20171120_1108'),
]

operations = [
migrations.CreateModel(
name='Webmention',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('source', models.CharField(max_length=255)),
('target', models.CharField(max_length=255)),
('state', models.CharField(choices=[('p', 'pending'), ('v', 'valid'), ('i', 'invalid'), ('d', 'deleted')], default='p', max_length=1)),
('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mentions', to='entries.Entry')),
],
options={
'default_related_name': 'mentions',
},
),
migrations.AlterUniqueTogether(
name='webmention',
unique_together=set([('source', 'target')]),
),
]

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


+ 31
- 0
webmention/models.py View File

@@ -0,0 +1,31 @@
from django.db import models
from entries.models import Entry
from model_utils.models import TimeStampedModel


class State:
PENDING = 'p'
VALID = 'v'
INVALID = 'i'
DELETED = 'd'
CHOICES = (
(PENDING, 'pending'),
(VALID, 'valid'),
(INVALID, 'invalid'),
(DELETED, 'deleted'),
)


class Webmention(TimeStampedModel):
entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
source = models.CharField(max_length=255)
target = models.CharField(max_length=255)
state = models.CharField(
choices=State.CHOICES,
default=State.PENDING,
max_length=1
)

class Meta:
default_related_name = 'mentions'
unique_together = ('source', 'target')

+ 8
- 0
webmention/urls.py View File

@@ -0,0 +1,8 @@
from django.conf.urls import url
from . import views

app_name = 'webmention'
urlpatterns = (
url('^$', views.accept, name='accept'),
url('^s/(?P<id>\d+)$', views.status, name='status')
)

+ 68
- 0
webmention/views.py View File

@@ -0,0 +1,68 @@
from django.http import HttpResponse
from django.urls import resolve, reverse, Resolver404
from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST
from entries.models import Entry
from lemoncurry.utils import bad_req
from urllib.parse import urljoin, urlparse

from .models import State, Webmention


@csrf_exempt
@require_POST
def accept(request):
if 'source' not in request.POST:
return bad_req('missing source url')
source_url = request.POST['source']

if 'target' not in request.POST:
return bad_req('missing target url')
target_url = request.POST['target']

source = urlparse(source_url)
target = urlparse(target_url)
if source.scheme not in ('http', 'https'):
return bad_req('unsupported source scheme')
if target.scheme not in ('http', 'https'):
return bad_req('unsupported target scheme')
if target.netloc != request.site.domain:
return bad_req('target not on this site')
origin = 'https://' + target.netloc

try:
match = resolve(target.path)
except Resolver404:
return bad_req('target not found')

if match.view_name != 'entries:entry':
return bad_req('target does not accept webmentions')

try:
entry = Entry.objects.get(pk=match.kwargs['id'])
except Entry.DoesNotExist:
return bad_req('target not found')

try:
mention = Webmention.objects.get(source=source_url, target=target_url)
except Webmention.DoesNotExist:
mention = Webmention()
mention.source = source_url
mention.target = target_url

mention.entry = entry
mention.state = State.PENDING
mention.save()
status = reverse('webmention:status', kwargs={'id': mention.id})

res = HttpResponse(status=201)
res['Location'] = urljoin(origin, status)
return res


@csrf_exempt
@require_GET
def status(request, id):
mention = get_object_or_404(Webmention.objects, pk=id)
return HttpResponse(mention.get_state_display())

Loading…
Cancel
Save