Beispiel #1
0
    def test_encode(self):
        recv_key = pyelliptic.ECC(curve="prime256v1")
        subscription_info = self._gen_subscription_info(recv_key)
        data = "Mary had a little lamb, with some nice mint jelly"
        push = WebPusher(subscription_info)
        encoded = push.encode(data)

        keyid = base64.urlsafe_b64encode(recv_key.get_pubkey()[1:])

        http_ece.keys[keyid] = recv_key
        http_ece.labels[keyid] = 'P-256'

        # Convert these b64 strings into their raw, binary form.
        raw_salt = base64.urlsafe_b64decode(push._repad(encoded['salt']))
        raw_dh = base64.urlsafe_b64decode(push._repad(encoded['crypto_key']))
        raw_auth = base64.urlsafe_b64decode(
            push._repad(subscription_info['keys']['auth']))

        decoded = http_ece.decrypt(
            buffer=encoded['body'],
            salt=raw_salt,
            dh=raw_dh,
            keyid=keyid,
            authSecret=raw_auth
            )

        eq_(decoded, data)
 def test_encode(self):
     for content_encoding in ["aesgcm", "aes128gcm"]:
         recv_key = ec.generate_private_key(
             ec.SECP256R1, default_backend())
         subscription_info = self._gen_subscription_info(recv_key)
         data = "Mary had a little lamb, with some nice mint jelly"
         push = WebPusher(subscription_info)
         encoded = push.encode(data, content_encoding=content_encoding)
         """
         crypto_key = base64.urlsafe_b64encode(
             self._get_pubkey_str(recv_key)
         ).strip(b'=')
         """
         # Convert these b64 strings into their raw, binary form.
         raw_salt = None
         if 'salt' in encoded:
             raw_salt = base64.urlsafe_b64decode(
                 push._repad(encoded['salt']))
         raw_dh = None
         if content_encoding != "aes128gcm":
             raw_dh = base64.urlsafe_b64decode(
                 push._repad(encoded['crypto_key']))
         raw_auth = base64.urlsafe_b64decode(
             push._repad(subscription_info['keys']['auth']))
         decoded = http_ece.decrypt(
             encoded['body'],
             salt=raw_salt,
             dh=raw_dh,
             private_key=recv_key,
             auth_secret=raw_auth,
             version=content_encoding
             )
         eq_(decoded.decode('utf8'), data)
def send(reg_id, message, **kwargs):
    """
    Site: https://developers.google.com
    API: https://developers.google.com/web/updates/2016/03/web-push-encryption
    Desc: Web Push notifications for Chrome and FireFox

    Installation:
    pip install 'pywebpush>=0.4.0'
    """

    subscription_info = kwargs.pop('subscription_info')

    payload = {
        "title": kwargs.pop("event"),
        "body": message,
        "url": kwargs.pop("push_url", None)
    }
    payload.update(kwargs)

    wp = WebPusher(subscription_info)
    response = wp.send(
        dumps(payload), gcm_key=settings.GCM_KEY,
        ttl=kwargs.pop("ttl", 60))

    if not response.ok or (
            response.text and loads(response.text).get("failure") > 0):
        raise GCMError(response.text)
    return True
Beispiel #4
0
def queue_push_firefox(apids, message):
    payload_data = json.loads(message)
    ttl = 7 * 86400
    apids_to_clear = []
    for apid in apids:
        subscription = json.loads(apid.token)
        wp = WebPusher(subscription)
        response = wp.send(json.dumps(payload_data), ttl=ttl)
        if response.status_code == 410:
            apids_to_clear.append(apid)

    # Remove unregistered ids
    for apid in apids_to_clear:
        apid.delete()
 def test_gcm(self, mock_post):
     subscription_info = self._gen_subscription_info(
         None,
         endpoint="https://android.googleapis.com/gcm/send/regid123")
     headers = {"Crypto-Key": "pre-existing",
                "Authentication": "bearer vapid"}
     data = "Mary had a little lamb"
     wp = WebPusher(subscription_info)
     wp.send(data, headers, gcm_key="gcm_key_value")
     pdata = json.loads(mock_post.call_args[1].get('data'))
     pheaders = mock_post.call_args[1].get('headers')
     eq_(pdata["registration_ids"][0], "regid123")
     eq_(pheaders.get("authorization"), "key=gcm_key_value")
     eq_(pheaders.get("content-type"), "application/json")
Beispiel #6
0
def queue_push_chrome(apids, message):
    payload_data = json.loads(message)
    ttl = 7 * 86400
    apids_to_clear = []
    for apid in apids:
        subscription = json.loads(apid.token)
        wp = WebPusher(subscription)
        response = wp.send(json.dumps(payload_data), gcm_key=settings.GCM_KEY, ttl=ttl)

        if response.status_code == 400:
            content = response.content
            if content.find("UnauthorizedRegistration") >= 0:
                apids_to_clear.append(apid)

    # Remove unregistered ids
    for apid in apids_to_clear:
        apid.delete()
Beispiel #7
0
    def test_init(self):
        # use static values so we know what to look for in the reply
        subscription_info = {
            u"endpoint": u"https://example.com/",
            u"keys": {
                u"p256dh": (u"BOrnIslXrUow2VAzKCUAE4sIbK00daEZCswOcf8m3T"
                            "F8V82B-OpOg5JbmYLg44kRcvQC1E2gMJshsUYA-_zMPR8"),
                u"auth": u"k8JV6sjdbhAi1n3_LDBLvA"
            }
        }
        rk_decode = (b'\x04\xea\xe7"\xc9W\xadJ0\xd9P3(%\x00\x13\x8b'
                     b'\x08l\xad4u\xa1\x19\n\xcc\x0eq\xff&\xdd1'
                     b'|W\xcd\x81\xf8\xeaN\x83\x92[\x99\x82\xe0\xe3'
                     b'\x89\x11r\xf4\x02\xd4M\xa00\x9b!\xb1F\x00'
                     b'\xfb\xfc\xcc=\x1f')
        self.assertRaises(
            WebPushException,
            WebPusher,
            {"keys": {'p256dh': 'AAA=', 'auth': 'AAA='}})
        self.assertRaises(
            WebPushException,
            WebPusher,
            {"endpoint": "https://example.com", "keys": {'p256dh': 'AAA='}})
        self.assertRaises(
            WebPushException,
            WebPusher,
            {"endpoint": "https://example.com", "keys": {'auth': 'AAA='}})
        self.assertRaises(
            WebPushException,
            WebPusher,
            {"endpoint": "https://example.com",
             "keys": {'p256dh': 'AAA=', 'auth': 'AAA='}})

        push = WebPusher(subscription_info)
        eq_(push.subscription_info, subscription_info)
        eq_(push.receiver_key, rk_decode)
        eq_(push.auth_key, b'\x93\xc2U\xea\xc8\xddn\x10"\xd6}\xff,0K\xbc')
Beispiel #8
0
    def send_message(self, message="", **kwargs):
        """Send a message to a user."""
        import jwt
        from pywebpush import WebPusher

        timestamp = int(time.time())
        tag = str(uuid.uuid4())

        payload = {
            'badge': '/static/images/notification-badge.png',
            'body': message,
            ATTR_DATA: {},
            'icon': '/static/icons/favicon-192x192.png',
            ATTR_TAG: tag,
            'timestamp': (timestamp*1000),  # Javascript ms since epoch
            ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
        }

        data = kwargs.get(ATTR_DATA)

        if data:
            # Pick out fields that should go into the notification directly vs
            # into the notification data dictionary.

            data_tmp = {}

            for key, val in data.items():
                if key in HTML5_SHOWNOTIFICATION_PARAMETERS:
                    payload[key] = val
                else:
                    data_tmp[key] = val

            payload[ATTR_DATA] = data_tmp

        if (payload[ATTR_DATA].get(ATTR_URL) is None and
                payload.get(ATTR_ACTIONS) is None):
            payload[ATTR_DATA][ATTR_URL] = URL_ROOT

        targets = kwargs.get(ATTR_TARGET)

        if not targets:
            targets = self.registrations.keys()

        for target in list(targets):
            info = self.registrations.get(target)
            if info is None:
                _LOGGER.error("%s is not a valid HTML5 push notification"
                              " target", target)
                continue

            jwt_exp = (datetime.datetime.fromtimestamp(timestamp) +
                       datetime.timedelta(days=JWT_VALID_DAYS))
            jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH]
            jwt_claims = {'exp': jwt_exp, 'nbf': timestamp,
                          'iat': timestamp, ATTR_TARGET: target,
                          ATTR_TAG: payload[ATTR_TAG]}
            jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8')
            payload[ATTR_DATA][ATTR_JWT] = jwt_token

            response = WebPusher(info[ATTR_SUBSCRIPTION]).send(
                json.dumps(payload), gcm_key=self._gcm_key, ttl='86400')

            # pylint: disable=no-member
            if response.status_code == 410:
                _LOGGER.info("Notification channel has expired")
                reg = self.registrations.pop(target)
                if not _save_config(self.registrations_json_path,
                                    self.registrations):
                    self.registrations[target] = reg
                    _LOGGER.error("Error saving registration.")
                else:
                    _LOGGER.info("Configuration saved")
Beispiel #9
0
    def _push_message(self, payload, **kwargs):
        """Send the message."""
        from pywebpush import WebPusher

        timestamp = int(time.time())
        ttl = int(kwargs.get(ATTR_TTL, DEFAULT_TTL))
        priority = kwargs.get(ATTR_PRIORITY, DEFAULT_PRIORITY)
        if priority not in ['normal', 'high']:
            priority = DEFAULT_PRIORITY
        payload['timestamp'] = (timestamp * 1000)  # Javascript ms since epoch
        targets = kwargs.get(ATTR_TARGET)

        if not targets:
            targets = self.registrations.keys()

        for target in list(targets):
            info = self.registrations.get(target)
            try:
                info = REGISTER_SCHEMA(info)
            except vol.Invalid:
                _LOGGER.error(
                    "%s is not a valid HTML5 push notification"
                    " target", target)
                continue
            payload[ATTR_DATA][ATTR_JWT] = add_jwt(
                timestamp, target, payload[ATTR_TAG],
                info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH])
            import jwt
            jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH]
            jwt_exp = (datetime.fromtimestamp(timestamp) +
                       timedelta(days=JWT_VALID_DAYS))
            jwt_claims = {
                'exp': jwt_exp,
                'nbf': timestamp,
                'iat': timestamp,
                ATTR_TARGET: target,
                ATTR_TAG: payload[ATTR_TAG]
            }
            jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8')
            payload[ATTR_DATA][ATTR_JWT] = jwt_token
            webpusher = WebPusher(info[ATTR_SUBSCRIPTION])
            if self._vapid_prv and self._vapid_email:
                vapid_headers = create_vapid_headers(self._vapid_email,
                                                     info[ATTR_SUBSCRIPTION],
                                                     self._vapid_prv)
                vapid_headers.update({
                    'urgency': priority,
                    'priority': priority
                })
                response = webpusher.send(data=json.dumps(payload),
                                          headers=vapid_headers,
                                          ttl=ttl)
            else:
                # Only pass the gcm key if we're actually using GCM
                # If we don't, notifications break on FireFox
                gcm_key = self._gcm_key \
                    if 'googleapis.com' \
                    in info[ATTR_SUBSCRIPTION][ATTR_ENDPOINT] \
                    else None
                response = webpusher.send(json.dumps(payload),
                                          gcm_key=gcm_key,
                                          ttl=ttl)

            if response.status_code == 410:
                _LOGGER.info("Notification channel has expired")
                reg = self.registrations.pop(target)
                if not save_json(self.registrations_json_path,
                                 self.registrations):
                    self.registrations[target] = reg
                    _LOGGER.error("Error saving registration")
                else:
                    _LOGGER.info("Configuration saved")
Beispiel #10
0
 def test_encode_empty(self):
     subscription_info = self._gen_subscription_info()
     headers = {"Crypto-Key": "pre-existing",
                "Authentication": "bearer vapid"}
     encoded = WebPusher(subscription_info).encode('', headers)
     eq_(encoded, None)
Beispiel #11
0
        "p256dh":
        "BK7h-R0UgDeT89jhWi76-FlTtlEr3DbVBnrr34qmK91Husli_Fazu7vo7kW1mg9F_qhNzrs2glbrc6wfqGFsXks=",
        "auth": "CyOHiGNXPcT5Slo9UMx2uA=="
    }
}
data = 'aaaaaa'
vapid_private_key = PRIVATE_KEY = '''
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2xyYpqQhOaIdSbqH
UwM3+ySvF47MoJyAFUNaHM7g/zOhRANCAAT554ztzCpjiIFOxNfEIicSzNPOZTIB
Y1+CGl+LDfM5RlUNERFdfZYRqMmwvX7ydq7UiASkspWqdVVKZnLCzPD3  
'''.strip()
vapid_claims = {"sub": "mailto:[email protected]"}

vapid_headers = None
if vapid_claims:
    if not vapid_claims.get('aud'):
        url = urlparse(subscription_info.get('endpoint'))
        aud = "{}://{}".format(url.scheme, url.netloc)
        vapid_claims['aud'] = aud
    if os.path.isfile(vapid_private_key):
        vv = Vapid.from_file(
            private_key_file=vapid_private_key)  # pragma no cover
    else:
        vv = Vapid.from_string(private_key=vapid_private_key)
    vapid_headers = vv.sign(vapid_claims)

result = WebPusher(subscription_info,
                   requests_session).send(data, vapid_headers)

print result.text
Beispiel #12
0
        for target in list(targets):
            info = self.registrations.get(target)
            try:
                info = REGISTER_SCHEMA(info)
            except vol.Invalid:
                _LOGGER.error(
                    "%s is not a valid HTML5 push notification target", target)
                continue
            payload[ATTR_DATA][ATTR_JWT] = add_jwt(
                timestamp,
                target,
                payload[ATTR_TAG],
                info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH],
            )
            webpusher = WebPusher(info[ATTR_SUBSCRIPTION])
            if self._vapid_prv and self._vapid_email:
                vapid_headers = create_vapid_headers(self._vapid_email,
                                                     info[ATTR_SUBSCRIPTION],
                                                     self._vapid_prv)
                vapid_headers.update({
                    "urgency": priority,
                    "priority": priority
                })
                response = webpusher.send(data=json.dumps(payload),
                                          headers=vapid_headers,
                                          ttl=ttl)
            else:
                # Only pass the gcm key if we're actually using GCM
                # If we don't, notifications break on FireFox
                gcm_key = (self._gcm_key if "googleapis.com"
Beispiel #13
0
 def push(self, message: str, icon: str = '/static/favicon.ico') -> None:
     WebPusher(self.to_dict()).send(
         json.dumps({'body': message,
                     'icon': icon}),
         gcm_key=GOOGLE_API_KEY)
	print "Please provide a message"
	exit()
else:
	print "\nMessage: " + args.message
	print "Title: " + args.title

if not args.school:
	print "Sending to all schools"
	students = Student.objects.all()
else:
	students = Student.objects.filter(personaltimetable__courses__school=args.school).distinct() | Student.objects.filter(school=args.school).distinct()
	print "Sending to <" + args.school + "> student(s)"
tokens = RegistrationToken.objects.filter(student__in=students)
print "This will send to ", tokens.count(), "device(s)"

confirmation = raw_input("Would you like to send?  (Y/n)")
if confirmation.lower() != "y":
	print "Quitting...\n"
	exit()

for token in tokens:
	subscription_info = {"endpoint":"https://android.googleapis.com/gcm/send/fuKkKlzSEEE:APA91bFKiuJ3LEx7Ke3xOEJ…Lx-t8INikH97ewASUn6OSzRAGeGf8Eu1B5Q7Lju_7QBj5VeGjwCePUufhiSzFXqEogaJ4esAqA","keys":{"p256dh":token.p256dh,"auth":token.auth}}
	data = '{"data": {"message":"'  + args.message + '","title":"' + args.title + '"}}'
	encoded = WebPusher(subscription_info).encode(data)
	crypto_key = "dh=" + encoded["crypto_key"]
	salt = "salt=" + encoded['salt']
	headers = {'Authorization': 'key=' + FCM_KEY, 'Content-Type': 'application/json', }
	headers.update({'crypto-key': crypto_key, 'content-encoding': 'aesgcm', 'encryption': salt})    
	fcm_data = {"raw_data": base64.b64encode(encoded.get('body')), "registration_ids": [token.endpoint[40:]]}
	resp = requests.post(FCM_URL, data=json.dumps(fcm_data), headers=headers)
Beispiel #15
0
 def get_webpusher(subscription):
     '''Return a WebPusher instance for a given subscription.'''
     return WebPusher(subscription)
Beispiel #16
0
    def send_message(self, message="", **kwargs):
        """Send a message to a user."""
        import jwt
        from pywebpush import WebPusher

        timestamp = int(time.time())
        tag = str(uuid.uuid4())

        payload = {
            'badge': '/static/images/notification-badge.png',
            'body': message,
            ATTR_DATA: {},
            'icon': '/static/icons/favicon-192x192.png',
            ATTR_TAG: tag,
            'timestamp': (timestamp * 1000),  # Javascript ms since epoch
            ATTR_TITLE: kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
        }

        data = kwargs.get(ATTR_DATA)

        if data:
            # Pick out fields that should go into the notification directly vs
            # into the notification data dictionary.

            for key, val in data.copy().items():
                if key in HTML5_SHOWNOTIFICATION_PARAMETERS:
                    payload[key] = val
                    del data[key]

            payload[ATTR_DATA] = data

        if (payload[ATTR_DATA].get(ATTR_URL) is None
                and payload.get(ATTR_ACTIONS) is None):
            payload[ATTR_DATA][ATTR_URL] = URL_ROOT

        targets = kwargs.get(ATTR_TARGET)

        if not targets:
            targets = self.registrations.keys()
        elif not isinstance(targets, list):
            targets = [targets]

        for target in targets:
            info = self.registrations.get(target)
            if info is None:
                _LOGGER.error(
                    '%s is not a valid HTML5 push notification'
                    ' target!', target)
                continue

            jwt_exp = (datetime.datetime.fromtimestamp(timestamp) +
                       datetime.timedelta(days=JWT_VALID_DAYS))
            jwt_secret = info[ATTR_SUBSCRIPTION][ATTR_KEYS][ATTR_AUTH]
            jwt_claims = {
                'exp': jwt_exp,
                'nbf': timestamp,
                'iat': timestamp,
                ATTR_TARGET: target,
                ATTR_TAG: payload[ATTR_TAG]
            }
            jwt_token = jwt.encode(jwt_claims, jwt_secret).decode('utf-8')
            payload[ATTR_DATA][ATTR_JWT] = jwt_token

            WebPusher(info[ATTR_SUBSCRIPTION]).send(json.dumps(payload),
                                                    gcm_key=self._gcm_key,
                                                    ttl='86400')
Beispiel #17
0
confirmation = raw_input("Would you like to send?  (Y/n)")
if confirmation.lower() != "y":
    print "Quitting...\n"
    exit()

for token in tokens:
    subscription_info = {
        "endpoint":
        "https://android.googleapis.com/gcm/send/fuKkKlzSEEE:APA91bFKiuJ3LEx7Ke3xOEJ…Lx-t8INikH97ewASUn6OSzRAGeGf8Eu1B5Q7Lju_7QBj5VeGjwCePUufhiSzFXqEogaJ4esAqA",
        "keys": {
            "p256dh": token.p256dh,
            "auth": token.auth
        }
    }
    data = '{"data": {"message":"' + args.message + '","title":"' + args.title + '"}}'
    encoded = WebPusher(subscription_info).encode(data)
    crypto_key = "dh=" + encoded["crypto_key"]
    salt = "salt=" + encoded['salt']
    headers = {
        'Authorization': 'key=' + FCM_KEY,
        'Content-Type': 'application/json',
    }
    headers.update({
        'crypto-key': crypto_key,
        'content-encoding': 'aesgcm',
        'encryption': salt
    })
    fcm_data = {
        "raw_data": base64.b64encode(encoded.get('body')),
        "registration_ids": [token.endpoint[40:]]
    }
Beispiel #18
0
with open('config.json') as config_file:
    config = json.load(config_file)

# Use grønstrøm API to get contents of push message
message = requests.get('https://grønstrøm.nu/api/v1/next-day-short').text
logging.info(f'Sending push message: {message}')

# Get all subscriptions from SQLite database
conn = sqlite3.Connection('/data/subs.db')
try:
    c = conn.cursor()
    for i, row in enumerate(c.execute('SELECT * FROM subs')):
        try:
            # Manually recreate the push facade from the pywebpush API to be able to specify both TTL and urgency
            subscription_info = json.loads(row[0])
            pusher = WebPusher(subscription_info)
            url = urlparse(subscription_info['endpoint'])
            aud = "{}://{}".format(url.scheme, url.netloc)
            vapid_claims = {
                'sub': f'mailto:{config["sub_email"]}',
                'aud': aud,
                'exp': int(time.time()) + 12 * 60 * 60
            }
            vv = Vapid.from_string(config['vapid_key'])
            headers = vv.sign(vapid_claims)
            # Define the urgency to be "normal", corresponding to messages being delivered
            # while the device is "On neither power nor wifi".
            # https://tools.ietf.org/html/draft-ietf-webpush-protocol-12#section-5.3
            headers['Urgency'] = 'normal'
            resp = pusher.send(message, headers, ttl=12 * 60 * 60)
            # TODO: Handle cases where response status code is not 201.