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 test_hasher(self): import autopush.db as db db.key_hash = "SuperSikkret" v = db.hasher("01234567123401234123456789ABCDEF") eq_(v, "0530bb351921e7b4be66831e4c126c6" + "d8f614d06cdd592cb8470f31177c8331a") db.key_hash = ""
def test_properties(self): eq_(self.base.uaid, "") eq_(self.base.chid, "") self.base.uaid = dummy_uaid eq_(self.base._client_info["uaid_hash"], hasher(dummy_uaid)) self.base.chid = dummy_chid eq_(self.base._client_info['channelID'], dummy_chid)
def test_properties(self): eq_(self.base.uaid, "") eq_(self.base.chid, "") self.base.uaid = dummy_uaid eq_(self.base._client_info["uaid_hash"], hasher(dummy_uaid)) self.base.chid = dummy_chid eq_(self.base._client_info['channelID'], dummy_chid)
def _write_endpoint(self, endpoint, uaid, chid, router_type, router_data, new_uaid=False): # type: (str, uuid.UUID, str, str, JSONDict, bool) -> None """Write the JSON response of the created endpoint""" response = dict(channelID=chid, endpoint=endpoint) if new_uaid: secret = None if self.conf.bear_hash_key: secret = generate_hash(self.conf.bear_hash_key[0], uaid.hex) response.update(uaid=uaid.hex, secret=secret) # Apply any router specific fixes to the outbound response. router = self.routers[router_type] router.amend_endpoint_response(response, router_data) self.set_header("Content-Type", "application/json") self.write(json.dumps(response)) self.log.info("Register", client_info=self._client_info, endpoint=endpoint, uaid_hash=hasher(uaid.hex)) self.finish()
def post(self, *args, **kwargs): # Store Vapid info if present jwt = self.valid_input.get("jwt") if jwt: self._client_info["jwt_crypto_key"] = jwt["jwt_crypto_key"] for i in jwt["jwt_data"]: self._client_info["jwt_" + i] = jwt["jwt_data"][i] user_data = self.valid_input["subscription"]["user_data"] router = self.ap_settings.routers[user_data["router_type"]] notification = self.valid_input["notification"] self._client_info["message_id"] = notification.message_id self._client_info["uaid"] = hasher(user_data.get("uaid")) self._client_info["channel_id"] = user_data.get("chid") self._client_info["router_key"] = user_data["router_type"] self._client_info["message_size"] = len(notification.data or "") self._client_info["ttl"] = notification.ttl self._client_info["version"] = notification.version self._router_time = time.time() d = Deferred() d.addCallback(router.route_notification, user_data) d.addCallback(self._router_completed, user_data, "") d.addErrback(self._router_fail_err) d.addErrback(self._response_err) # Call the prepared router d.callback(notification)
def process(self, command): # type: (Register) -> Union[RegisterResponse, RegisterErrorResponse] valid, msg = _validate_chid(command.channel_id) if not valid: return RegisterErrorResponse(error_msg=msg) endpoint = self.conf.make_endpoint( command.uaid.hex, command.channel_id, command.key ) message = self.db.message_tables[command.message_month] try: message.register_channel(command.uaid.hex, command.channel_id) except ClientError as ex: if (ex.response['Error']['Code'] == "ProvisionedThroughputExceededException"): return RegisterErrorResponse(error_msg="overloaded", status=503) self.metrics.increment('ua.command.register') log.info( "Register", channel_id=command.channel_id, endpoint=endpoint, uaid_hash=hasher(command.uaid.hex), ) return RegisterResponse(endpoint=endpoint)
def _delete_channel(self, uaid, chid): if not self.db.message.unregister_channel(uaid.hex, chid): raise ItemNotFound("ChannelID not found") self.log.info("Unregister", client_info=self._client_info, channel_id=chid, uaid_hash=hasher(uaid))
def test_hasher(self): import autopush.db as db db.key_hash = "SuperSikkret" v = db.hasher("01234567123401234123456789ABCDEF") assert v == ('0530bb351921e7b4be66831e4c126c6' 'd8f614d06cdd592cb8470f31177c8331a') db.key_hash = ""
def _router_completed(self, response, uaid_data, warning="", router_type=None, vapid=None): """Called after router has completed successfully""" # Log the time taken for routing self._timings["route_time"] = time.time() - self._router_time # Were we told to update the router data? time_diff = time.time() - self._start_time if response.router_data is not None: if not response.router_data: # An empty router_data object indicates that the record should # be deleted. There is no longer valid route information for # this record. self.log.debug(format="Dropping User", code=100, uaid_hash=hasher(uaid_data["uaid"]), uaid_record=dump_uaid(uaid_data), client_info=self._client_info) d = deferToThread(self.db.router.drop_user, uaid_data["uaid"]) d.addCallback(lambda x: self._router_response(response)) return d # The router data needs to be updated to include any changes # requested by the bridge system uaid_data["router_data"] = response.router_data # set the AWS mandatory data uaid_data["connected_at"] = ms_time() d = deferToThread(self.db.router.register_user, uaid_data) response.router_data = None d.addCallback(lambda x: self._router_completed( response, uaid_data, warning, router_type, vapid)) return d else: # No changes are requested by the bridge system, proceed as normal dest = 'Direct' if response.status_code == 200 or response.logged_status == 200: self.log.debug(format="Successful delivery", client_info=self._client_info) elif response.status_code == 202 or response.logged_status == 202: self.log.debug(format="Router miss, message stored.", client_info=self._client_info) dest = 'Stored' self.metrics.timing("notification.request_time", duration=time_diff) self.metrics.increment('notification.message.success', tags=make_tags(destination=dest, router=router_type, vapid=(vapid is not None))) response.response_body = (response.response_body + " " + warning).strip() self._router_response(response)
def drop_user(self, uaid, uaid_record, code): # type: (str, dict, int) -> None """Drop a user record""" log.debug( "Dropping User", code=code, uaid_hash=hasher(uaid), uaid_record=repr(uaid_record) ) self.metrics.increment('ua.expiration', tags=['code:{}'.format(code)]) self.db.router.drop_user(uaid)
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 _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 post( self, subscription, # type: Dict[str, Any] notification, # type: WebPushNotification jwt=None, # type: Optional[JSONDict] **kwargs # type: Any ): # type: (...) -> Deferred # Store Vapid info if present if jwt: self.metrics.increment("updates.vapid.{}".format( kwargs.get('vapid_version'))) self._client_info["jwt_crypto_key"] = jwt["jwt_crypto_key"] for i in jwt["jwt_data"]: self._client_info["jwt_" + i] = jwt["jwt_data"][i] user_data = subscription["user_data"] encoding = '' if notification.data and notification.headers: encoding = notification.headers.get('encoding', '') self.metrics.increment( "updates.notification.encoding.{}".format(encoding)) self._client_info.update( message_id=notification.message_id, uaid_hash=hasher(user_data.get("uaid")), channel_id=notification.channel_id.hex, router_key=user_data["router_type"], message_size=notification.data_length, message_ttl=notification.ttl, version=notification.version, encoding=encoding, ) router_type = user_data["router_type"] router = self.routers[router_type] self._router_time = time.time() d = maybeDeferred(router.route_notification, notification, user_data) d.addCallback(self._router_completed, user_data, "", router_type=router_type, vapid=jwt) d.addErrback(self._router_fail_err, router_type=router_type, vapid=jwt is not None, uaid=user_data.get("uaid")) d.addErrback(self._response_err) return d
def process_hello(self, data): """Process a hello message""" # This must be a helo, or we kick the client cmd = data.get("messageType") if cmd != "hello": return self.sendClose() if self.ps.uaid: return self.returnError("hello", "duplicate hello", 401) uaid = data.get("uaid") self.ps.use_webpush = data.get("use_webpush", False) self.ps._base_tags.append("use_webpush:%s" % self.ps.use_webpush) self.ps.router_type = "webpush" if self.ps.use_webpush\ else "simplepush" if self.ps.use_webpush: self.ps.updates_sent = defaultdict(lambda: []) self.ps.direct_updates = defaultdict(lambda: []) existing_user, uaid = validate_uaid(uaid) self.ps.uaid = uaid self.ps.uaid_hash = hasher(uaid) # Check for the special wakeup commands if "wakeup_host" in data and "mobilenetwork" in data: wakeup_host = data.get("wakeup_host") if "ip" in wakeup_host and "port" in wakeup_host: mobilenetwork = data.get("mobilenetwork") # Normalize the wake info to a single object. wake_data = dict(data=dict(ip=wakeup_host["ip"], port=wakeup_host["port"], mcc=mobilenetwork.get("mcc", ''), mnc=mobilenetwork.get("mnc", ''), netid=mobilenetwork.get("netid", ''))) self.ps.wake_data = wake_data self.transport.pauseProducing() d = self._register_user(existing_user) d.addCallback(self._copy_new_data) d.addCallback(self._check_collision) d.addErrback(self.trap_cancel) d.addErrback(self.err_overload, "hello") d.addErrback(self.err_hello) self.ps._register = d return d
def process_hello(self, data): """Process a hello message""" # This must be a helo, or we kick the client cmd = data.get("messageType") if cmd != "hello": return self.sendClose() if self.ps.uaid: return self.returnError("hello", "duplicate hello", 401) uaid = data.get("uaid") self.ps.use_webpush = data.get("use_webpush", False) self.ps._base_tags.append("use_webpush:%s" % self.ps.use_webpush) self.ps.router_type = "webpush" if self.ps.use_webpush\ else "simplepush" if self.ps.use_webpush: self.ps.updates_sent = defaultdict(lambda: []) self.ps.direct_updates = defaultdict(lambda: []) existing_user, uaid = validate_uaid(uaid) self.ps.uaid = uaid self.ps.uaid_hash = hasher(uaid) # Check for the special wakeup commands if "wakeup_host" in data and "mobilenetwork" in data: wakeup_host = data.get("wakeup_host") if "ip" in wakeup_host and "port" in wakeup_host: mobilenetwork = data.get("mobilenetwork") # Normalize the wake info to a single object. wake_data = dict( data=dict(ip=wakeup_host["ip"], port=wakeup_host["port"], mcc=mobilenetwork.get("mcc", ''), mnc=mobilenetwork.get("mnc", ''), netid=mobilenetwork.get("netid", ''))) self.ps.wake_data = wake_data self.transport.pauseProducing() d = self._register_user(existing_user) d.addCallback(self._copy_new_data) d.addCallback(self._check_collision) d.addErrback(self.trap_cancel) d.addErrback(self.err_overload, "hello") d.addErrback(self.err_hello) self.ps._register = d return d
def put(self, subscription, version, data): # type: (Dict[str, Any], str, str) -> Deferred user_data = subscription["user_data"] self._client_info.update(uaid_hash=hasher(user_data.get("uaid")), channel_id=user_data.get("chid"), message_id=str(version), router_key=user_data["router_type"]) notification = Notification( version=version, data=data, channel_id=str(subscription["chid"]), ) router = self.routers[user_data["router_type"]] d = maybeDeferred(router.route_notification, notification, user_data) d.addCallback(self._router_completed, user_data, "") d.addErrback(self._router_fail_err) d.addErrback(self._response_err) return d
def put(self, *args, **kwargs): sub = self.valid_input["subscription"] user_data = sub["user_data"] router = self.ap_settings.routers[user_data["router_type"]] self._client_info["uaid"] = hasher(user_data.get("uaid")) self._client_info["channel_id"] = user_data.get("chid") self._client_info["message_id"] = self.valid_input["version"] self._client_info["router_key"] = user_data["router_type"] notification = Notification( version=self.valid_input["version"], data=self.valid_input["data"], channel_id=str(sub["chid"]), ) d = Deferred() d.addCallback(router.route_notification, user_data) d.addCallback(self._router_completed, user_data, "") d.addErrback(self._router_fail_err) d.addErrback(self._response_err) # Call the prepared router d.callback(notification)
def process(self, command # type: Unregister ): # type: (...) -> Union[UnregisterResponse, UnregisterErrorResponse] valid, msg = _validate_chid(command.channel_id) if not valid: return UnregisterErrorResponse(error_msg=msg) message = self.db.message_tables[command.message_month] # TODO: JSONResponseError not handled (no force_retry) message.unregister_channel(command.uaid.hex, command.channel_id) # TODO: Clear out any existing tracked messages for this # channel self.metrics.increment('ua.command.unregister') # TODO: user/raw_agent? log.info( "Unregister", channel_id=command.channel_id, uaid_hash=hasher(command.uaid.hex), **dict(code=command.code) if command.code else {} ) return UnregisterResponse()
def uaid(self, value): """Set the UAID and update the uaid hash""" self._uaid = value self.uaid_hash = hasher(value) self._client_info["uaid_hash"] = self.uaid_hash
def uaid(self, value): """Set the UAID and update the uaid hash""" self._uaid = value self.uaid_hash = hasher(value) self._client_info["uaid_hash"] = self.uaid_hash
def _delete_uaid(self, uaid): self.log.debug(format="Dropping User", code=101, uaid_hash=hasher(uaid.hex)) if not self.db.router.drop_user(uaid.hex): raise ItemNotFound("UAID not found")