def __init__(self, name, sygnal, config): super(WebpushPushkin, self).__init__(name, sygnal, config) nonunderstood = self.cfg.keys() - self.UNDERSTOOD_CONFIG_FIELDS if nonunderstood: logger.warning( "The following configuration fields are not understood: %s", nonunderstood, ) self.http_pool = HTTPConnectionPool(reactor=sygnal.reactor) self.max_connections = self.get_config("max_connections", DEFAULT_MAX_CONNECTIONS) self.connection_semaphore = DeferredSemaphore(self.max_connections) self.http_pool.maxPersistentPerHost = self.max_connections tls_client_options_factory = ClientTLSOptionsFactory() # use the Sygnal global proxy configuration proxy_url = sygnal.config.get("proxy") self.http_agent = ProxyAgent( reactor=sygnal.reactor, pool=self.http_pool, contextFactory=tls_client_options_factory, proxy_url_str=proxy_url, ) self.http_agent_wrapper = HttpAgentWrapper(self.http_agent) self.allowed_endpoints = None # type: Optional[List[Pattern]] allowed_endpoints = self.get_config("allowed_endpoints") if allowed_endpoints: if not isinstance(allowed_endpoints, list): raise PushkinSetupException( "'allowed_endpoints' should be a list or not set") self.allowed_endpoints = list(map(glob_to_regex, allowed_endpoints)) privkey_filename = self.get_config("vapid_private_key") if not privkey_filename: raise PushkinSetupException( "'vapid_private_key' not set in config") if not os.path.exists(privkey_filename): raise PushkinSetupException( "path in 'vapid_private_key' does not exist") try: self.vapid_private_key = Vapid.from_file( private_key_file=privkey_filename) except VapidException as e: raise PushkinSetupException( "invalid 'vapid_private_key' file") from e self.vapid_contact_email = self.get_config("vapid_contact_email") if not self.vapid_contact_email: raise PushkinSetupException( "'vapid_contact_email' not set in config") self.ttl = self.get_config("ttl", DEFAULT_TTL) if not isinstance(self.ttl, int): raise PushkinSetupException("'ttl' must be an int if set")
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="aesgcm", curl=False, timeout=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: Path to vapid private key PEM or encoded str :type vapid_private_key: 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 :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_private_key: raise WebPushException("VAPID dict missing 'private_key'") if 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_raw(private_raw=vapid_private_key.encode()) vapid_headers = vv.sign(vapid_claims) result = WebPusher(subscription_info).send( data, vapid_headers, content_encoding=content_encoding, curl=curl, timeout=timeout, ) if not curl and result.status_code > 202: raise WebPushException("Push failed: {}: {}".format( result, result.text)) return result
def webpush(subscription_info, data=None, vapid_private_key=None, vapid_claims=None, content_encoding="aesgcm", curl=False): """ 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: Path to vapid private key PEM or encoded str :type vapid_private_key: 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 :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_private_key: raise WebPushException("VAPID dict missing 'private_key'") if 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_raw(private_raw=vapid_private_key.encode()) vapid_headers = vv.sign(vapid_claims) result = WebPusher(subscription_info).send( data, vapid_headers, content_encoding=content_encoding, curl=curl, ) if not curl and result.status_code > 202: raise WebPushException("Push failed: {}: {}".format( result, result.text)) return result
"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