Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
 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)
Ejemplo n.º 9
0
 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
Ejemplo n.º 10
0
 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)
Ejemplo n.º 11
0
 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)
Ejemplo n.º 12
0
 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,
         )
Ejemplo n.º 13
0
 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)
Ejemplo n.º 14
0
 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)
Ejemplo n.º 15
0
 def load_body(self, value):
     try:
         return json.loads(value)
     except ValueError:
         raise InvalidRequest("Invalid Request body",
                              status_code=400,
                              errno=108)
Ejemplo n.º 16
0
 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)
Ejemplo n.º 17
0
 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
Ejemplo n.º 18
0
    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"),
        )
Ejemplo n.º 19
0
 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,
         )
Ejemplo n.º 20
0
 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
Ejemplo n.º 21
0
 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)
Ejemplo n.º 22
0
 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)
Ejemplo n.º 23
0
 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,
         )
Ejemplo n.º 24
0
    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
Ejemplo n.º 25
0
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()
Ejemplo n.º 26
0
    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)
Ejemplo n.º 27
0
 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)
Ejemplo n.º 28
0
    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)
Ejemplo n.º 29
0
 def invalid_content_encoding(self, d):
     raise InvalidRequest("Unknown Content-Encoding",
                          status_code=400,
                          errno=110)
Ejemplo n.º 30
0
 def validate_ttl(self, value):
     if value is not None and value < 0:
         raise InvalidRequest("TTL must be greater than 0", errno=114)