Compare commits
2 Commits
master
...
accept-sub
Author | SHA1 | Date | |
---|---|---|---|
|
b6b9254514 | ||
|
56b9daeed9 |
21
LICENSE
21
LICENSE
@ -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.
|
|
@ -1 +1,3 @@
|
|||||||
Migrated to https://git.startinblox.com/djangoldp-packages/djangoldp-webpushnotifications.
|
# djangoldp-webpushnotification
|
||||||
|
|
||||||
|
> WIP
|
||||||
|
@ -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)
|
|
@ -1,5 +0,0 @@
|
|||||||
"""This module is loaded by DjangoLDP core during setup."""
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'webpush'
|
|
||||||
]
|
|
@ -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)
|
|
@ -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)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
@ -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"=")
|
|
18
djangoldp_webpushnotification/templates/sw.js
Normal file
18
djangoldp_webpushnotification/templates/sw.js
Normal 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",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
@ -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)
|
|
@ -1,10 +0,0 @@
|
|||||||
"""YAML configurations for djangoldp_webpushnotification testing."""
|
|
||||||
|
|
||||||
yaml_config = """
|
|
||||||
dependencies:
|
|
||||||
|
|
||||||
ldppackages:
|
|
||||||
- djangoldp_account
|
|
||||||
- djangoldp_webpushnotification
|
|
||||||
- djangoldp_webpushnotification.tests
|
|
||||||
"""
|
|
@ -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)
|
|
@ -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)
|
|
@ -1,9 +1,15 @@
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from .views import send_push
|
from .views import home, send_push
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("send_push", send_push),
|
path("send_push", send_push),
|
||||||
path("webpush/", include("webpush.urls")),
|
path("webpush/", include("webpush.urls")),
|
||||||
|
path(
|
||||||
|
"sw.js",
|
||||||
|
TemplateView.as_view(
|
||||||
|
template_name="sw.js", content_type="application/x-javascript"
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
@ -5,11 +5,8 @@ from django.http.response import HttpResponse, JsonResponse
|
|||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_GET, require_POST
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
|
|
||||||
from webpush import send_user_notification
|
|
||||||
|
|
||||||
from djangoldp_account.models import LDPUser
|
from djangoldp_account.models import LDPUser
|
||||||
from djangoldp_webpushnotification.models import VAPIDKeyset
|
from webpush import send_user_notification
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@ -25,12 +22,6 @@ def send_push(request):
|
|||||||
user_id = data["id"]
|
user_id = data["id"]
|
||||||
user = get_object_or_404(LDPUser, pk=user_id)
|
user = get_object_or_404(LDPUser, pk=user_id)
|
||||||
payload = {"head": data["head"], "body": data["body"]}
|
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)
|
send_user_notification(user=user, payload=payload, ttl=1000)
|
||||||
|
|
||||||
return JsonResponse(status=200, data={"message": "Web push successful"})
|
return JsonResponse(status=200, data={"message": "Web push successful"})
|
||||||
|
@ -10,13 +10,10 @@ license = MIT
|
|||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
install_requires =
|
install_requires =
|
||||||
djangoldp~=2.1
|
djangoldp~=0.5
|
||||||
djangoldp_account~=2.1
|
|
||||||
ecdsa~=0.16.1
|
|
||||||
django-webpush~=0.3
|
django-webpush~=0.3
|
||||||
|
|
||||||
[options.extras_require]
|
[options.extras_require]
|
||||||
include_package_data = True
|
|
||||||
dev =
|
dev =
|
||||||
factory_boy>=2.11.0
|
factory_boy>=2.11.0
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user