Compare commits

...
This repository has been archived on 2021-04-21. You can view files and clone it, but cannot push or open issues or pull requests.

34 Commits

Author SHA1 Message Date
decentral1se 46f95e405f Add migration notice 2021-04-21 15:56:20 +02:00
3wc 9caf97966c Load django-webpush VAPID keys from DB 2021-04-15 18:57:22 +02:00
3wc 0fdbd1f3b2 Fix TypeError rendering VAPIDKeyset.public_key 2021-04-15 18:57:05 +02:00
decentral1se c64d1029b4
Add failing test for missing VAPID key 2021-04-15 15:06:37 +02:00
decentral1se a0c0aa760d
Merge imports 2021-04-15 14:28:34 +02:00
decentral1se c433a7d561
Fixup subscription test to use a VAPIDKeyset 2021-04-15 14:28:20 +02:00
decentral1se 59ce5900e0
Appease formatter 2021-04-15 14:27:58 +02:00
decentral1se 63c7384e50
Don't call tobytes on bytes 2021-04-15 14:27:44 +02:00
decentral1se aaf365dba2
Add webpush model checks into sub test
Let the formatter have its way also.
2021-04-15 09:42:16 +02:00
3wc 7ee6ac7691 Make TestAcceptSubscription work..
..by using Django default test client instead of the one from
`rest_framework`.
2021-04-14 21:34:31 +02:00
decentral1se fbb58b1737
Some WIP progress on writing webpush tests 2021-04-08 16:59:00 +02:00
decentral1se 6542993bf0
Fix name of test 2021-04-08 16:29:40 +02:00
decentral1se 74b2712699
Test VAPIDKeyset can generate a public key 2021-04-08 16:28:49 +02:00
3wc d03672e01e Don't try and reference views.home 2021-04-08 15:37:23 +02:00
Fay Arnold 2760e70c8e remove js template 2021-04-08 14:20:14 +01:00
decentral1se 5c13b626a8
Resolve tabbing/indentation issues 2021-04-07 17:18:09 +02:00
decentral1se 44153bc562
Re-work access to pub/priv keys for showing 2021-04-07 17:14:16 +02:00
3wc 64015d9b5b Remove VAPIDKeyset.public_key, generate on demand
Also rename `gen_vapid_keys` to `gen_vapid_key`, and merge migrations
2021-04-07 17:07:21 +02:00
decentral1se 6ac2905a6c
Make note of core 2021-04-07 16:53:58 +02:00
decentral1se 1843946342
Fix wave of typos 2021-04-07 16:53:24 +02:00
decentral1se 91fbb3dde1
Add boilerplate for unit testing 2021-04-07 16:51:55 +02:00
decentral1se 2fb474c87c
Throw down some docs in that README 2021-04-07 16:42:45 +02:00
3wc c05a8e65dc Fix gen_vapid_keys 2021-04-07 16:26:37 +02:00
decentral1se 89680cf6f1
Drop boilerplate 2021-04-07 16:21:29 +02:00
3wc 2f6a345e5c Fix admin.py 2021-04-07 16:19:50 +02:00
Fay Arnold 4aa1d000b9 Merge branch 'master' of https://git.autonomic.zone/autonomic-cooperative/djangoldp-webpushnotification 2021-04-07 15:18:48 +01:00
Fay Arnold bee22e5e5e add webpush as installed_app 2021-04-07 15:18:11 +01:00
decentral1se d3492dc266
Add license file 2021-04-07 16:13:55 +02:00
trav 4fd70faf99 merged changes hopefully not broooken 2021-04-07 10:11:30 -04:00
trav cf6c0ed1d0 moved files 2021-04-07 10:08:01 -04:00
decentral1se b86872be24
Add django-webpush to deps 2021-04-07 15:57:33 +02:00
decentral1se 764e9aaa3f
Initial boilerplate for accepting subscriptions 2021-04-07 15:56:09 +02:00
3wc a8492491b9 Fix models.py 2021-04-07 15:52:32 +02:00
trav 9d19e0e73c added in this stuff https://git.startinblox.com/djangoldp-packages/djangoldp-notification/merge_requests/43#note_59310 2021-04-07 09:44:56 -04:00
14 changed files with 288 additions and 4 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Startin blox
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,3 +1 @@
# djangoldp-webpushnotification
> WIP
Migrated to https://git.startinblox.com/djangoldp-packages/djangoldp-webpushnotifications.

View File

@ -0,0 +1,19 @@
from django.contrib import admin
from djangoldp.admin import DjangoLDPAdmin
from .models import VAPIDKeyset
class VAPIDKeysetAdmin(DjangoLDPAdmin):
readonly_fields = ('public_key_view', 'private_key_view')
def public_key_view(self, obj):
return obj.public_key
def private_key_view(self, obj):
return obj.private_key.tobytes()
class Meta:
verbose_name = 'VAPID key-set'
admin.site.register(VAPIDKeyset, VAPIDKeysetAdmin)

View File

@ -0,0 +1,5 @@
"""This module is loaded by DjangoLDP core during setup."""
INSTALLED_APPS = [
'webpush'
]

View File

@ -0,0 +1,9 @@
from django.urls import include, path
from django.views.generic import TemplateView
from .views import send_push
urlpatterns = [
path("send_push", send_push),
path("webpush/", include("webpush.urls")),
]

View File

@ -0,0 +1,21 @@
from base64 import urlsafe_b64encode
import ecdsa
from django.core.management.base import BaseCommand
from djangoldp_webpushnotification.models import VAPIDKeyset
from ecdsa import SigningKey
class Command(BaseCommand):
help = "Generate VAPID key pair"
def handle(self, *args, **options):
priv_key = SigningKey.generate(curve=ecdsa.NIST256p)
VAPIDKeyset.objects.create(
private_key=urlsafe_b64encode(priv_key.to_string()).strip(b"=")
)
self.stdout.write("VAPID Keyset succesfully generated")
exit(0)

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.19 on 2021-04-07 14:38
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='VAPIDKeyset',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('private_key', models.BinaryField(max_length=43)),
],
),
]

View File

@ -0,0 +1,23 @@
from base64 import urlsafe_b64decode, urlsafe_b64encode
from django.db import models
from ecdsa import NIST256p, SigningKey
class VAPIDKeyset(models.Model):
private_key = models.BinaryField(max_length=43)
def __str__(self):
return "public_key:{}... private_key:{}...".format(
self.public_key[:10], self.private_key[:10]
)
@property
def public_key(self):
key_str = self.private_key.tobytes()
padding = len(key_str) % 4
key_str += b"=" * padding
key = SigningKey.from_string(
urlsafe_b64decode(key_str), curve=NIST256p
).get_verifying_key()
return urlsafe_b64encode(b"\x04" + key.to_string()).strip(b"=")

View File

@ -0,0 +1,23 @@
import sys
import django
import yaml
from django.conf import settings as django_settings
from djangoldp.conf.ldpsettings import LDPSettings
from djangoldp_webpushnotification.tests.settings_default import yaml_config
config = yaml.safe_load(yaml_config)
ldpsettings = LDPSettings(config)
django_settings.configure(ldpsettings)
django.setup()
from django.test.runner import DiscoverRunner
test_runner = DiscoverRunner(verbosity=1)
failures = test_runner.run_tests([
'djangoldp_webpushnotification.tests.tests_vapidkeyset',
'djangoldp_webpushnotification.tests.tests_accept_subscription',
])
if failures:
sys.exit(failures)

View File

@ -0,0 +1,10 @@
"""YAML configurations for djangoldp_webpushnotification testing."""
yaml_config = """
dependencies:
ldppackages:
- djangoldp_account
- djangoldp_webpushnotification
- djangoldp_webpushnotification.tests
"""

View File

@ -0,0 +1,78 @@
import json
from base64 import urlsafe_b64encode
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from djangoldp_webpushnotification.models import VAPIDKeyset
from ecdsa import NIST256p, SigningKey
from webpush.models import PushInformation, SubscriptionInfo
class TestAcceptSubscription(TestCase):
def setUp(self):
self.client = Client()
self.user = get_user_model().objects.create(
username="john", email="jlennon@beatles.com", password="glass onion"
)
self.client.force_login(self.user)
def tearDown(self):
self.user.delete()
def gen_vapid_key(self):
generated = SigningKey.generate(curve=NIST256p)
encoded = urlsafe_b64encode(generated.to_string()).strip(b"=")
return VAPIDKeyset.objects.create(private_key=encoded)
def test_accept_sub(self):
vapid_key_set = self.gen_vapid_key()
payload = {
"status_type": "subscribe",
"subscription": {
"endpoint": "https://hubl.example.com",
"keys": {
"auth": "front-end-generated-secret",
"p256dh": vapid_key_set.public_key.decode("utf-8"),
},
},
"browser": "firefox",
}
url = reverse("save_webpush_info")
response = self.client.post(
url, data=json.dumps(payload), content_type="application/json"
)
self.assertEqual(response.status_code, 201)
sub_info = SubscriptionInfo.objects.get()
self.assertEqual(sub_info.browser, "firefox")
self.assertEqual(sub_info.endpoint, "https://hubl.example.com")
self.assertEqual(sub_info.auth, "front-end-generated-secret")
self.assertEqual(sub_info.p256dh, vapid_key_set.public_key.decode("utf-8"))
push_info = PushInformation.objects.get()
self.assertEqual(push_info.user, self.user)
self.assertEqual(push_info.subscription, sub_info)
def test_accept_sub_missing_vapid_key(self):
payload = {
"status_type": "subscribe",
"subscription": {
"endpoint": "https://hubl.example.com",
"keys": {
"auth": "front-end-generated-secret",
"p256dh": "INVALID-PUBLIC-KEY",
},
},
"browser": "firefox",
}
url = reverse("save_webpush_info")
response = self.client.post(
url, data=json.dumps(payload), content_type="application/json"
)
self.assertEqual(response.status_code, 403)

View File

@ -0,0 +1,14 @@
from base64 import urlsafe_b64encode
from django.test import TestCase
from djangoldp_webpushnotification.models import VAPIDKeyset
from ecdsa import NIST256p, SigningKey
class TestVAPIDKeySet(TestCase):
def test_vapidkeyset_public_key(self):
priv_key = SigningKey.generate(curve=NIST256p)
vapid_key_set = VAPIDKeyset.objects.create(
private_key=urlsafe_b64encode(priv_key.to_string()).strip(b"=")
)
assert isinstance(vapid_key_set.public_key, bytes)

View File

@ -0,0 +1,38 @@
import json
from django.conf import settings
from django.http.response import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST
from webpush import send_user_notification
from djangoldp_account.models import LDPUser
from djangoldp_webpushnotification.models import VAPIDKeyset
@require_POST
@csrf_exempt
def send_push(request):
try:
body = request.body
data = json.loads(body)
if "head" not in data or "body" not in data or "id" not in data:
return JsonResponse(status=400, data={"message": "Invalid data format"})
user_id = data["id"]
user = get_object_or_404(LDPUser, pk=user_id)
payload = {"head": data["head"], "body": data["body"]}
vapid_key = VAPIDKeyset.objects.first()
settings.WEBPUSH_SETTINGS = {
'VAPID_PUBLIC_KEY': vapid_key.public_key,
'VAPID_PRIVATE_KEY': vapid_key.private_key.tobytes().decode(),
'VAPID_ADMIN_EMAIL': 'foo@bar.com',
}
send_user_notification(user=user, payload=payload, ttl=1000)
return JsonResponse(status=200, data={"message": "Web push successful"})
except TypeError:
return JsonResponse(status=500, data={"message": "An error occurred"})

View File

@ -10,9 +10,13 @@ license = MIT
[options]
packages = find:
install_requires =
djangoldp~=0.5
djangoldp~=2.1
djangoldp_account~=2.1
ecdsa~=0.16.1
django-webpush~=0.3
[options.extras_require]
include_package_data = True
dev =
factory_boy>=2.11.0