def validate_uaid_month_and_chid(self, d): db = self.context["db"] # type: DatabaseManager try: result = db.router.get_uaid(d["uaid"].hex) except ItemNotFound: raise InvalidRequest("UAID not found", status_code=410, errno=103) if result.get("router_type") not in ["webpush", "gcm", "apns", "fcm"]: raise InvalidRequest("Wrong URL for user", errno=108) if (result.get("router_type") in ["gcm", "fcm"] and 'senderID' not in result.get('router_data', {}).get( "creds", {})): # Make sure we note that this record is bad. result['critical_failure'] = \ result.get('critical_failure', "Missing SenderID") db.router.register_user(result) if result.get("critical_failure"): raise InvalidRequest("Critical Failure: %s" % result.get("critical_failure"), status_code=410, errno=105) if result["router_type"] == "webpush": self._validate_webpush(d, result) # Propagate the looked up user data back out d["user_data"] = result
def validate_auth(self, data): request_pref_header = {'www-authenticate': PREF_SCHEME} auth = data["headers"].get("authorization") if not auth: raise InvalidRequest("Unauthorized", status_code=401, errno=109, headers=request_pref_header) try: auth_type, auth_token = re.sub( r' +', ' ', auth.strip()).split(" ", 2) except ValueError: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header) if auth_type.lower() not in AUTH_SCHEMES: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header) conf = self.context['conf'] uaid = data["path_kwargs"]["uaid"] if conf.bear_hash_key: is_valid = False for key in conf.bear_hash_key: test_token = generate_hash(key, uaid.hex) is_valid |= constant_time.bytes_eq(bytes(test_token), bytes(auth_token)) if not is_valid: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header)
def _validate_webpush(self, d, result): db = self.context["db"] # type: DatabaseManager log = self.context["log"] # type: Logger channel_id = normalize_id(d["chid"]) uaid = result["uaid"] if 'current_month' not in result: log.info(format="Dropping User", code=102, uaid_hash=hasher(uaid), uaid_record=dump_uaid(result)) db.router.drop_user(uaid) raise InvalidRequest("No such subscription", status_code=410, errno=106) month_table = result["current_month"] if month_table not in db.message_tables: log.info(format="Dropping User", code=103, uaid_hash=hasher(uaid), uaid_record=dump_uaid(result)) db.router.drop_user(uaid) raise InvalidRequest("No such subscription", status_code=410, errno=106) exists, chans = db.message_tables[month_table].all_channels(uaid=uaid) if (not exists or channel_id.lower() not in map( lambda x: normalize_id(x), chans)): log.info("Unknown subscription: {channel_id}", channel_id=channel_id) raise InvalidRequest("No such subscription", status_code=410, errno=106)
def validate_topic(self, value): if value is None: return True if len(value) > 32: raise InvalidRequest("Topic must be no greater than 32 " "characters", errno=113) if not VALID_BASE64_URL.match(value): raise InvalidRequest("Topic must be URL and Filename safe Base" "64 alphabet", errno=113)
def validate_auth(self, d): auth = d["headers"].get("authorization") needs_auth = d["token_info"]["api_ver"] == "v2" if not needs_auth and not auth: return try: vapid_auth = parse_auth_header(auth) token = vapid_auth['t'] d["vapid_version"] = "draft{:0>2}".format(vapid_auth['version']) if vapid_auth['version'] == 2: public_key = vapid_auth['k'] else: public_key = d["subscription"].get("public_key") jwt = extract_jwt( token, public_key, is_trusted=self.context['settings'].enable_tls_auth) except (KeyError, ValueError, InvalidSignature, TypeError, VapidAuthException): raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if "exp" not in jwt: raise InvalidRequest("Invalid bearer token: No expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) try: jwt_expires = int(jwt['exp']) except ValueError: raise InvalidRequest("Invalid bearer token: Invalid expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) now = time.time() jwt_has_expired = now > jwt_expires if jwt_has_expired: raise InvalidRequest("Invalid bearer token: Auth expired", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24) if jwt_too_far_in_future: raise InvalidRequest( "Invalid bearer token: Auth > 24 hours in " "the future", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_crypto_key = base64url_encode(public_key) d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
def validate_uaid_chid(self, d): try: result = self.context["settings"].router.get_uaid(d["uaid"].hex) except ItemNotFound: raise InvalidRequest("UAID not found", status_code=410, errno=103) if result.get("router_type") != "simplepush": raise InvalidRequest("Wrong URL for user", errno=108) # Propagate the looked up user data back out d["user_data"] = result
def validate_uaid_month_and_chid(self, d): db = self.context["db"] # type: DatabaseManager try: result = db.router.get_uaid(d["uaid"].hex) except ItemNotFound: raise InvalidRequest("UAID not found", status_code=410, errno=103) # We must have a router_type to validate the user router_type = result.get("router_type") if router_type not in VALID_ROUTER_TYPES: self.context["log"].debug(format="Dropping User", code=102, uaid_hash=hasher(result["uaid"]), uaid_record=repr(result)) self.context["metrics"].increment("updates.drop_user", tags=make_tags(errno=102)) self.context["db"].router.drop_user(result["uaid"]) raise InvalidRequest("No such subscription", status_code=410, errno=106) if (router_type == "gcm" and 'senderID' not in result.get( 'router_data', {}).get("creds", {})): # Make sure we note that this record is bad. result['critical_failure'] = \ result.get('critical_failure', "Missing SenderID") db.router.register_user(result) if (router_type == "fcm" and 'app_id' not in result.get('router_data', {})): # Make sure we note that this record is bad. result['critical_failure'] = \ result.get('critical_failure', "Missing SenderID") db.router.register_user(result) if result.get("critical_failure"): raise InvalidRequest("Critical Failure: %s" % result.get("critical_failure"), status_code=410, errno=105) # Some stored user records are marked as "simplepush". # If you encounter one, may need to tweak it a bit to get it as # a valid WebPush record. if result["router_type"] == "simplepush": result["router_type"] = "webpush" if result["router_type"] == "webpush": self._validate_webpush(d, result) # Propagate the looked up user data back out d["user_data"] = result
def extract_data(self, req): message_id = None if req['path_args']: message_id = req['path_args'][0] message_id = req['path_kwargs'].get('message_id', message_id) if not message_id: raise InvalidRequest("Missing Token", status_code=400) try: notif = WebPushNotification.from_message_id( bytes(message_id), fernet=self.context['settings'].fernet, ) except (InvalidToken, InvalidTokenException): raise InvalidRequest("Invalid message ID", status_code=400) return dict(notification=notif)
def extract_subscription(self, d): try: result = self.context["settings"].parse_endpoint( token=d["token"], version=d["api_ver"], ckey_header=d["ckey_header"], auth_header=d["auth_header"], ) except (VapidAuthException): raise InvalidRequest("missing authorization header", status_code=401, errno=109) except (InvalidTokenException, InvalidToken): raise InvalidRequest("invalid token", status_code=404, errno=102) return result
def validate_encryption(self, value): """Must contain a salt value""" salt = CryptoKey.parse_and_get_label(value, "salt") if not salt or not VALID_BASE64_URL.match(salt): raise InvalidRequest("Invalid salt value in Encryption header", status_code=400, errno=110)
def validate_crypto_key(self, value): if CryptoKey.parse_and_get_label(value, "dh"): raise InvalidRequest( "Do not include 'dh' in aes128gcm " "Crypto-Key header", status_code=400, errno=110)
def validate_data(self, value): max_data = self.context["settings"].max_data if value and len(value) > max_data: raise InvalidRequest( "Data payload must be smaller than {}".format(max_data), errno=104, )
def validate_crypto_key(self, value): """Must contain a dh value""" dh = CryptoKey.parse_and_get_label(value, "dh") if not dh or not VALID_BASE64_URL.match("dh"): raise InvalidRequest("Invalid dh value in Encryption-Key header", status_code=400, errno=110)
def test_validation_error(self): try: raise InvalidRequest("oops", errno=110) except InvalidRequest: fail = Failure() self.base._validation_err(fail) self.status_mock.assert_called_with(400, reason=None)
def load_body(self, value): try: return json.loads(value) except ValueError: raise InvalidRequest("Invalid Request body", status_code=400, errno=108)
def validate_encryption(self, value): if CryptoKey.parse_and_get_label(value, "salt"): raise InvalidRequest( "Do not include 'salt' in aes128gcm " "Encryption header", status_code=400, errno=110)
def _deserialize(self, value, attr, data): try: new_uuid = uuid.UUID(value) except ValueError: raise InvalidRequest("Invalid Path", status_code=404) else: return new_uuid
def extract_data(self, req): router_data = {} if req['body']: try: router_data = json.loads(req['body']) except ValueError: raise InvalidRequest("Invalid Request body", status_code=401, errno=108) # UAID and CHID may be empty. This can trigger different behaviors # in the handlers, so we can't set default values here. uaid = req['path_kwargs'].get('uaid') chid = req['path_kwargs'].get('chid', router_data.get("channelID")) if uaid: try: u_uuid = uuid.UUID(uaid) except (ValueError, TypeError): raise InvalidRequest("Invalid Request UAID", status_code=401, errno=109) # Check if the UAID has a 'critical error' which means that it's # probably invalid and should be reset/re-registered try: record = self.context['settings'].router.get_uaid(u_uuid.hex) if record.get('critical_failure'): raise InvalidRequest("Invalid Request UAID", status_code=410, errno=105) except ItemNotFound: pass if chid: try: uuid.UUID(chid) except (ValueError, TypeError): raise InvalidRequest("Invalid Request Channel_id", status_code=410, errno=106) return dict( uaid=uaid, chid=chid, router_type=req['path_kwargs'].get('router_type'), router_token=req['path_kwargs'].get('router_token'), router_data=router_data, auth=req.get('headers', {}).get("Authorization"), )
def reject_encryption_key(self, data, original_data): if "encryption-key" in original_data: raise InvalidRequest( "Encryption-Key header not valid for 02 or later " "webpush-encryption", status_code=400, errno=110, )
def extract_subscription(self, d): try: result = self.context["settings"].parse_endpoint( token=d["token"], version=d["api_ver"], ) except (InvalidTokenException, InvalidToken): raise InvalidRequest("invalid token", errno=102) return result
def register_router(self, data): router_type = data["path_kwargs"]["router_type"] router = self.context["routers"][router_type] try: router.register(uaid="", router_data=data["router_data"], app_id=data["path_kwargs"]["app_id"]) except RouterException as exc: raise InvalidRequest(exc.message, status_code=exc.status_code, errno=exc.errno, headers=exc.headers)
def extract_data(self, req): message_id = req['path_kwargs'].get('message_id') try: notif = WebPushNotification.from_message_id( bytes(message_id), fernet=self.context['conf'].fernet, ) except (InvalidToken, InvalidTokenException): raise InvalidRequest("Invalid message ID", status_code=400) return dict(notification=notif)
def validate_crypto_key(self, value): """Must not contain a dh value""" dh = CryptoKey.parse_and_get_label(value, "dh") if dh: raise InvalidRequest( "dh value in Crypto-Key header not valid for 01 or earlier " "webpush-encryption", status_code=400, errno=110, )
def validate_uaid_month_and_chid(self, d): settings = self.context["settings"] # type: AutopushSettings try: result = settings.router.get_uaid(d["uaid"].hex) except ItemNotFound: raise InvalidRequest("UAID not found", status_code=410, errno=103) if result.get("router_type") not in ["webpush", "gcm", "apns", "fcm"]: raise InvalidRequest("Wrong URL for user", errno=108) if result.get("critical_failure"): raise InvalidRequest("Critical Failure: %s" % result.get("critical_failure"), status_code=410, errno=105) if result["router_type"] == "webpush": self._validate_webpush(d, result) # Propagate the looked up user data back out d["user_data"] = result
def conditional_token_check(object_dict, parent_dict): ptype = parent_dict['path_kwargs']['type'] # Basic "bozo-filter" to prevent customer surprises later. if ptype not in ['apns', 'fcm', 'gcm', 'webpush', 'test']: raise InvalidRequest("Unknown registration type", status_code=400, errno=108, ) if ptype in ['gcm', 'fcm']: return GCMTokenSchema() if ptype == 'apns': return APNSTokenSchema() return TokenSchema()
def _validate_webpush(self, d, result): db = self.context["db"] # type: DatabaseManager log = self.context["log"] # type: Logger metrics = self.context["metrics"] # type: IMetrics channel_id = normalize_id(d["chid"]) uaid = result["uaid"] if 'current_month' not in result: log.debug(format="Dropping User", code=102, uaid_hash=hasher(uaid), uaid_record=repr(result)) metrics.increment("updates.drop_user", tags=make_tags(errno=102)) db.router.drop_user(uaid) raise InvalidRequest("No such subscription", status_code=410, errno=106) month_table = result["current_month"] if month_table not in db.message_tables: log.debug(format="Dropping User", code=103, uaid_hash=hasher(uaid), uaid_record=repr(result)) metrics.increment("updates.drop_user", tags=make_tags(errno=103)) db.router.drop_user(uaid) raise InvalidRequest("No such subscription", status_code=410, errno=106) msg = db.message_table(month_table) exists, chans = msg.all_channels(uaid=uaid) if (not exists or channel_id.lower() not in map( lambda x: normalize_id(x), chans)): log.debug("Unknown subscription: {channel_id}", channel_id=channel_id) raise InvalidRequest("No such subscription", status_code=410, errno=106)
def validate_data(self, data): settings = self.context['settings'] try: data['router'] = settings.routers[data['router_type']] except KeyError: raise InvalidRequest("Invalid router", status_code=400, errno=108) if data.get('uaid'): request_pref_header = {'www-authenticate': PREF_SCHEME} try: settings.router.get_uaid(data['uaid'].hex) except ItemNotFound: raise InvalidRequest("UAID not found", status_code=410, errno=103) if not data.get('auth'): raise InvalidRequest("Unauthorized", status_code=401, errno=109, headers=request_pref_header) settings = self.context['settings'] try: auth_type, auth_token = re.sub(r' +', ' ', data['auth'].strip()).split( " ", 2) except ValueError: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header) if auth_type.lower() not in AUTH_SCHEMES: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header) if settings.bear_hash_key: is_valid = False for key in settings.bear_hash_key: test_token = generate_hash(key, data['uaid'].hex) is_valid |= constant_time.bytes_eq(bytes(test_token), bytes(auth_token)) if not is_valid: raise InvalidRequest("Invalid Authentication", status_code=401, errno=109, headers=request_pref_header)
def validate_auth(self, d): auth = d["headers"].get("authorization") needs_auth = d["token_info"]["api_ver"] == "v2" if not auth and not needs_auth: return public_key = d["subscription"].get("public_key") try: auth_type, token = auth.split(' ', 1) except ValueError: raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) # If its not a bearer token containing what may be JWT, stop if auth_type.lower() not in AUTH_SCHEMES or '.' not in token: if needs_auth: raise InvalidRequest("Missing Authorization Header", status_code=401, errno=109) return try: jwt = extract_jwt(token, public_key) except (ValueError, InvalidSignature, TypeError): raise InvalidRequest("Invalid Authorization Header", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) if "exp" not in jwt: raise InvalidRequest("Invalid bearer token: No expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) try: jwt_expires = int(jwt['exp']) except ValueError: raise InvalidRequest("Invalid bearer token: Invalid expiration", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) now = time.time() jwt_has_expired = now > jwt_expires if jwt_has_expired: raise InvalidRequest("Invalid bearer token: Auth expired", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_too_far_in_future = (jwt_expires - now) > (60 * 60 * 24) if jwt_too_far_in_future: raise InvalidRequest( "Invalid bearer token: Auth > 24 hours in " "the future", status_code=401, errno=109, headers={"www-authenticate": PREF_SCHEME}) jwt_crypto_key = base64url_encode(public_key) d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)
def invalid_content_encoding(self, d): raise InvalidRequest("Unknown Content-Encoding", status_code=400, errno=110)
def validate_ttl(self, value): if value is not None and value < 0: raise InvalidRequest("TTL must be greater than 0", errno=114)