Compare commits

..

2 Commits

Author SHA1 Message Date
decentral1se b6b9254514
First stab at integrating push notifications logic 2021-04-07 15:35:47 +02:00
decentral1se 56b9daeed9
Add django-webpush pypi package 2021-04-07 15:30:51 +02:00
15 changed files with 30 additions and 251 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
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 +1,3 @@
Migrated to https://git.startinblox.com/djangoldp-packages/djangoldp-webpushnotifications.
# djangoldp-webpushnotification
> WIP

View File

@ -1,19 +0,0 @@
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

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

View File

@ -1,21 +0,0 @@
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

@ -1,21 +0,0 @@
# 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

@ -1,23 +0,0 @@
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,18 @@
// Register event listener for the 'push' event.
self.addEventListener("push", function (event) {
// Retrieve the textual payload from event.data (a PushMessageData object).
// Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
// on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
const eventInfo = event.data.text();
const data = JSON.parse(eventInfo);
const head = data.head || "New Notification 🕺🕺";
const body = data.body || "This is default content. Your notification didn't have one 🙄🙄";
// Keep the service worker alive until the notification is created.
event.waitUntil(
self.registration.showNotification(head, {
body: body,
icon: "https://i.imgur.com/MZM3K5w.png",
})
);
});

View File

@ -1,23 +0,0 @@
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

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

View File

@ -1,78 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,9 +1,15 @@
from django.urls import include, path
from django.views.generic import TemplateView
from .views import send_push
from .views import home, send_push
urlpatterns = [
path("send_push", send_push),
path("webpush/", include("webpush.urls")),
path(
"sw.js",
TemplateView.as_view(
template_name="sw.js", content_type="application/x-javascript"
),
),
]

View File

@ -5,11 +5,8 @@ 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
from webpush import send_user_notification
@require_POST
@ -25,12 +22,6 @@ def send_push(request):
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"})

View File

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