def create_vapid_headers(vapid_email, subscription_info, vapid_private_key): """Create encrypted headers to send to WebPusher.""" if vapid_email and vapid_private_key and ATTR_ENDPOINT in subscription_info: url = urlparse(subscription_info.get(ATTR_ENDPOINT)) vapid_claims = { "sub": f"mailto:{vapid_email}", "aud": f"{url.scheme}://{url.netloc}", } vapid = Vapid.from_string(private_key=vapid_private_key) return vapid.sign(vapid_claims) return None
def create_vapid_headers(vapid_email, subscription_info, vapid_private_key): """Create encrypted headers to send to WebPusher.""" from py_vapid import Vapid if vapid_email and vapid_private_key and ATTR_ENDPOINT in subscription_info: url = urlparse(subscription_info.get(ATTR_ENDPOINT)) vapid_claims = { "sub": "mailto:{}".format(vapid_email), "aud": "{}://{}".format(url.scheme, url.netloc), } vapid = Vapid.from_string(private_key=vapid_private_key) return vapid.sign(vapid_claims) return None
def create_vapid_headers(vapid_email, subscription_info, vapid_private_key): """Create encrypted headers to send to WebPusher.""" from py_vapid import Vapid try: from urllib.parse import urlparse except ImportError: # pragma: no cover from urlparse import urlparse if (vapid_email and vapid_private_key and ATTR_ENDPOINT in subscription_info): url = urlparse(subscription_info.get(ATTR_ENDPOINT)) vapid_claims = { 'sub': 'mailto:{}'.format(vapid_email), 'aud': "{}://{}".format(url.scheme, url.netloc) } vapid = Vapid.from_string(private_key=vapid_private_key) return vapid.sign(vapid_claims) return None
def webpush(subscription_info, data=None, vapid_private_key=None, vapid_claims=None, content_encoding="aes128gcm", curl=False, timeout=None, ttl=0, verbose=False, headers=None): """ One call solution to endcode and send `data` to the endpoint contained in `subscription_info` using optional VAPID auth headers. in example: .. code-block:: python from pywebpush import python webpush( subscription_info={ "endpoint": "https://push.example.com/v1/abcd", "keys": {"p256dh": "0123abcd...", "auth": "001122..."} }, data="Mary had a little lamb, with a nice mint jelly", vapid_private_key="path/to/key.pem", vapid_claims={"sub": "*****@*****.**"} ) No additional method call is required. Any non-success will throw a `WebPushException`. :param subscription_info: Provided by the client call :type subscription_info: dict :param data: Serialized data to send :type data: str :param vapid_private_key: Vapid instance or path to vapid private key PEM \ or encoded str :type vapid_private_key: Union[Vapid, str] :param vapid_claims: Dictionary of claims ('sub' required) :type vapid_claims: dict :param content_encoding: Optional content type string :type content_encoding: str :param curl: Return as "curl" string instead of sending :type curl: bool :param timeout: POST requests timeout :type timeout: float or tuple :param ttl: Time To Live :type ttl: int :param verbose: Provide verbose feedback :type verbose: bool :return requests.Response or string :param headers: Dictionary of extra HTTP headers to include :type headers: dict """ if headers is None: headers = dict() else: # Ensure we don't leak VAPID headers by mutating the passed in dict. headers = headers.copy() vapid_headers = None if vapid_claims: if verbose: print("Generating VAPID headers...") if not vapid_claims.get('aud'): url = urlparse(subscription_info.get('endpoint')) aud = "{}://{}".format(url.scheme, url.netloc) vapid_claims['aud'] = aud # Remember, passed structures are mutable in python. # It's possible that a previously set `exp` field is no longer valid. if (not vapid_claims.get('exp') or vapid_claims.get('exp') < int(time.time())): # encryption lives for 12 hours vapid_claims['exp'] = int(time.time()) + (12 * 60 * 60) if verbose: print("Setting VAPID expry to {}...".format( vapid_claims['exp'])) if not vapid_private_key: raise WebPushException("VAPID dict missing 'private_key'") if isinstance(vapid_private_key, Vapid): vv = vapid_private_key elif os.path.isfile(vapid_private_key): # Presume that key from file is handled correctly by # py_vapid. vv = Vapid.from_file( private_key_file=vapid_private_key) # pragma no cover else: vv = Vapid.from_string(private_key=vapid_private_key) if verbose: print("\t claims: {}".format(vapid_claims)) vapid_headers = vv.sign(vapid_claims) if verbose: print("\t headers: {}".format(vapid_headers)) headers.update(vapid_headers) response = WebPusher(subscription_info, verbose=verbose).send( data, headers, ttl=ttl, content_encoding=content_encoding, curl=curl, timeout=timeout, ) if not curl and response.status_code > 202: raise WebPushException("Push failed: {} {}\nResponse body:{}".format( response.status_code, response.reason, response.text), response=response) return response
def webpush(subscription_info, data=None, vapid_private_key=None, vapid_claims=None, content_encoding="aes128gcm", curl=False, timeout=None, ttl=0): """ One call solution to endcode and send `data` to the endpoint contained in `subscription_info` using optional VAPID auth headers. in example: .. code-block:: python from pywebpush import python webpush( subscription_info={ "endpoint": "https://push.example.com/v1/abcd", "keys": {"p256dh": "0123abcd...", "auth": "001122..."} }, data="Mary had a little lamb, with a nice mint jelly", vapid_private_key="path/to/key.pem", vapid_claims={"sub": "*****@*****.**"} ) No additional method call is required. Any non-success will throw a `WebPushException`. :param subscription_info: Provided by the client call :type subscription_info: dict :param data: Serialized data to send :type data: str :param vapid_private_key: Vapid instance or path to vapid private key PEM \ or encoded str :type vapid_private_key: Union[Vapid, str] :param vapid_claims: Dictionary of claims ('sub' required) :type vapid_claims: dict :param content_encoding: Optional content type string :type content_encoding: str :param curl: Return as "curl" string instead of sending :type curl: bool :param timeout: POST requests timeout :type timeout: float or tuple :param ttl: Time To Live :type ttl: int :return requests.Response or string """ 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 not vapid_claims.get('exp'): # encryption lives for 12 hours vapid_claims['exp'] = int(time.time()) + (12 * 60 * 60) if not vapid_private_key: raise WebPushException("VAPID dict missing 'private_key'") if isinstance(vapid_private_key, Vapid): vv = vapid_private_key elif os.path.isfile(vapid_private_key): # Presume that key from file is handled correctly by # py_vapid. 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) response = WebPusher(subscription_info).send( data, vapid_headers, ttl=ttl, content_encoding=content_encoding, curl=curl, timeout=timeout, ) if not curl and response.status_code > 202: raise WebPushException("Push failed: {} {}".format( response.status_code, response.reason), response=response) return response
"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
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. logging.debug( f'{i} ({resp.status_code}: {resp.text}): {subscription_info}') except Exception as e: logging.warning(f'{i} (failed): {e}') finally: if conn: conn.close()