def post(self): result, error = {}, None params = params_from_request(self.request) project = params.pop('project') method = params.pop('method') data = params.get('data') if data is not None: try: data = json_decode(data) except Exception as e: logger.error(e) else: params["data"] = data project = self.application.get_project(project) if not project: error = self.application.PROJECT_NOT_FOUND else: result, error = yield self.application.process_call(project, method, params) self.set_header("Content-Type", "application/json") self.finish(json_encode({ "body": result, "error": error }))
def on_api_message(self, redis_message): """ Got message from Redis, dispatch it into right message handler. """ try: message = json_decode(redis_message[1]) except ValueError: logger.error("Redis API - malformed JSON") return if not isinstance(message, dict): logger.error("Redis API - object expected") return project_id = message.get("project") if not project_id: logger.error("Redis API - project required") return data = message.get("data") if not data: logger.error("Redis API - data required") project, error = yield self.application.structure.get_project_by_id(project_id) if error: logger.error("Redis API - server error") return if not project: logger.error("Redis API - project not found") return _, error = yield self.application.process_api_data(project, data, False) if error: logger.error(error)
def get_history(self, project_id, channel): history_list_key = self.get_history_list_key(project_id, channel) try: data = yield Task(self.worker.lrange, history_list_key, 0, -1) except StreamClosedError as e: raise Return((None, e)) else: raise Return(([json_decode(x.decode()) for x in data], None))
def get_flash_message(self): """ Returns the flash message. """ cookie = self.get_secure_cookie(self.FLASH_COOKIE_NAME) if cookie: self.clear_cookie(self.FLASH_COOKIE_NAME) return json_decode(cookie) return None
def prepare_key_value(pair): if not pair: return key = pair[0].decode() try: value = json_decode(pair[1].decode()) except ValueError: value = {} return key, value
def decode_data(data): """ Decode request body received from API client. """ try: return json_decode(data) except Exception as err: logger.debug(err) return None
def post(self, project_key): """ Handle API HTTP requests. """ timer = None if self.application.collector: timer = self.application.collector.get_timer('api_time') if not self.request.body: raise tornado.web.HTTPError(400, log_message="empty request") if self.request.headers.get("Content-Type", "").startswith("application/json"): # handle JSON requests if corresponding Content-Type specified encoded_data = self.request.body sign = self.request.headers.get("X-API-Sign") else: # handle application/x-www-form-urlencoded request sign = self.get_argument('sign', None) encoded_data = self.get_argument('data', None) if not sign: raise tornado.web.HTTPError(400, log_message="no data sign") if not encoded_data: raise tornado.web.HTTPError(400, log_message="no data") project = self.application.get_project(project_key) if not project: raise tornado.web.HTTPError(404, log_message="project not found") # use project secret to validate sign secret = project['secret'] is_valid = auth.check_sign( secret, project_key, encoded_data, sign ) if not is_valid: raise tornado.web.HTTPError(401, log_message="unauthorized") try: data = json_decode(encoded_data) except Exception as err: logger.debug(err) raise tornado.web.HTTPError(400, log_message="malformed data") multi_response, error = yield self.application.process_api_data(project, data) if error: raise tornado.web.HTTPError(400, log_message=error) if self.application.collector: self.application.collector.incr('api') timer.stop() self.json_response(multi_response.as_message())
def update_channel_user_info(self, body, channel): """ Try to extract channel specific user info from response body and keep it for channel. """ try: info = json_decode(body) except Exception as e: logger.error(str(e)) info = {} self.channel_user_info[channel] = info
def on_redis_message(self, redis_message): """ Got message from Redis, dispatch it into right message handler. """ msg_type = redis_message[0] if six.PY3: msg_type = msg_type.decode() if msg_type != "message": return channel = redis_message[1] if six.PY3: channel = channel.decode() if channel == self.control_channel_name: yield self.handle_control_message(json_decode(redis_message[2])) elif channel == self.admin_channel_name: yield self.handle_admin_message(json_decode(redis_message[2])) else: yield self.handle_message(channel, redis_message[2])
def on_redis_message(self, redis_message): """ Got message from Redis, dispatch it into right message handler. """ msg_type = redis_message[0] if six.PY3: msg_type = msg_type.decode() if msg_type != 'message': return channel = redis_message[1] if six.PY3: channel = channel.decode() if channel == self.control_channel_name: yield self.handle_control_message(json_decode(redis_message[2])) elif channel == self.admin_channel_name: yield self.handle_admin_message(json_decode(redis_message[2])) else: yield self.handle_message(channel, redis_message[2])
def update_channel_info(self, body, channel): """ Try to extract channel specific user info from response body and keep it for channel. """ try: info = json_decode(body) except Exception as e: logger.error(str(e)) info = {} self.channel_info[channel] = info
def message_received(self, message): """ Called when message from client received. """ multi_response = MultiResponse() try: data = json_decode(message) except ValueError: logger.error('malformed JSON data') yield self.close_sock() raise Return((True, None)) if isinstance(data, dict): # single object request response, err = yield self.process_obj(data) multi_response.add(response) if err: # error occurred, connection must be closed logger.error(err) yield self.sock.send(multi_response.as_message()) yield self.close_sock() raise Return((True, None)) elif isinstance(data, list): # multiple object request if len(data) > self.application.CLIENT_API_MESSAGE_LIMIT: logger.info("client API message limit exceeded") yield self.close_sock() raise Return((True, None)) for obj in data: response, err = yield self.process_obj(obj) multi_response.add(response) if err: # close connection in case of any error logger.error(err) yield self.sock.send(multi_response.as_message()) yield self.send_disconnect_message() yield self.close_sock() raise Return((True, None)) else: logger.error('data not list and not dictionary') yield self.close_sock() raise Return((True, None)) yield self.send(multi_response.as_message()) raise Return((True, None))
def post_actions(self): params = params_from_request(self.request) method = params.pop('method') params.pop('_xsrf') data = params.get('data', None) if data is not None: try: data = json_decode(data) except Exception as e: logger.error(e) else: params["data"] = data result, error = yield self.application.process_call( self.project, method, params) self.set_header("Content-Type", "application/json") self.finish(json_encode({"body": result, "error": error}))
def on_message(self, message): """ The only method supported at moment - auth - used to authorize websocket connection. """ try: data = json_decode(message) except ValueError: self.close() return try: method = data["method"] params = data["params"] except (TypeError, KeyError): self.close() return if method == "auth": try: token = params["token"] except (KeyError, TypeError): self.close() return else: user = decode_signed_value( self.application.settings['cookie_secret'], 'token', token ) if user: self.subscribe() self.send(json_encode({ "method": "auth", "body": True })) else: self.send(json_encode({ "method": "auth", "body": False })) self.close() return else: self.close() return
def post(self): json_data = self.get_argument("data") data = json_decode(json_data) res, err = yield self.application.structure.clear_structure() if err: raise tornado.web.HTTPError(500, log_message=str(err)) for project in data.get("projects", []): res, err = yield self.application.structure.project_create(**project) if err: raise tornado.web.HTTPError(500, log_message=str(err)) for namespace in data.get("namespaces", []): if namespace["project_id"] != project["_id"]: continue res, err = yield self.application.structure.namespace_create(project, **namespace) if err: raise tornado.web.HTTPError(500, log_message=str(err)) self.redirect(self.reverse_url("main"))
def post_actions(self): params = params_from_request(self.request) method = params.pop('method') params.pop('_xsrf') data = params.get('data', None) if data is not None: try: data = json_decode(data) except Exception as e: logger.error(e) else: params["data"] = data result, error = yield self.application.process_call(self.project, method, params) self.set_header("Content-Type", "application/json") self.finish(json_encode({ "body": result, "error": error }))
def post(self, project_id): """ Handle API HTTP requests. """ timer = None if self.application.collector: timer = self.application.collector.get_timer('api_time') if not self.request.body: raise tornado.web.HTTPError(400, log_message="empty request") if self.request.headers.get("Content-Type", "").startswith("application/json"): # handle JSON requests if corresponding Content-Type specified try: request_data = json_decode(self.request.body) except ValueError: raise tornado.web.HTTPError(400, log_message="malformed json") if not isinstance(request_data, dict): raise tornado.web.HTTPError(400, log_message="object expected") sign = request_data.get("sign") encoded_data = request_data.get("data") else: # handle application/x-www-form-urlencoded request sign = self.get_argument('sign', None) encoded_data = self.get_argument('data', None) if not sign: raise tornado.web.HTTPError(400, log_message="no data sign") if not encoded_data: raise tornado.web.HTTPError(400, log_message="no data") is_owner_request = False if project_id == self.application.OWNER_API_PROJECT_ID: # API request aims to be from superuser is_owner_request = True if is_owner_request: # use api secret key from configuration to check sign secret = self.application.config.get("api_secret") if not secret: raise tornado.web.HTTPError( 501, log_message="no api_secret in configuration file") project = None else: project, error = yield self.application.structure.get_project_by_id( project_id) if error: raise tornado.web.HTTPError(500, log_message=str(error)) if not project: raise tornado.web.HTTPError(404, log_message="project not found") # use project secret key to validate sign secret = project['secret_key'] is_valid = auth.check_sign(secret, project_id, encoded_data, sign) if not is_valid: raise tornado.web.HTTPError(401, log_message="unauthorized") try: data = json_decode(encoded_data) except Exception as err: logger.debug(err) raise tornado.web.HTTPError(400, log_message="malformed data") multi_response, error = yield self.application.process_api_data( project, data, is_owner_request) if error: raise tornado.web.HTTPError(400, log_message=error) if self.application.collector: self.application.collector.incr('api') timer.stop() self.json_response(multi_response.as_message())
def post(self, project_id): """ Handle API HTTP requests. """ timer = None if self.application.collector: timer = self.application.collector.get_timer('api_time') if not self.request.body: raise tornado.web.HTTPError(400, log_message="empty request") if self.request.headers.get("Content-Type", "").startswith("application/json"): # handle JSON requests if corresponding Content-Type specified try: request_data = json_decode(self.request.body) except ValueError: raise tornado.web.HTTPError(400, log_message="malformed json") if not isinstance(request_data, dict): raise tornado.web.HTTPError(400, log_message="object expected") sign = request_data.get("sign") encoded_data = request_data.get("data") else: # handle application/x-www-form-urlencoded request sign = self.get_argument('sign', None) encoded_data = self.get_argument('data', None) if not sign: raise tornado.web.HTTPError(400, log_message="no data sign") if not encoded_data: raise tornado.web.HTTPError(400, log_message="no data") is_owner_request = False if project_id == self.application.OWNER_API_PROJECT_ID: # API request aims to be from superuser is_owner_request = True if is_owner_request: # use api secret key from configuration to check sign secret = self.application.config.get("api_secret") if not secret: raise tornado.web.HTTPError(501, log_message="no api_secret in configuration file") project = None else: project, error = yield self.application.structure.get_project_by_id(project_id) if error: raise tornado.web.HTTPError(500, log_message=str(error)) if not project: raise tornado.web.HTTPError(404, log_message="project not found") # use project secret key to validate sign secret = project['secret_key'] is_valid = auth.check_sign( secret, project_id, encoded_data, sign ) if not is_valid: raise tornado.web.HTTPError(401, log_message="unauthorized") try: data = json_decode(encoded_data) except Exception as err: logger.debug(err) raise tornado.web.HTTPError(400, log_message="malformed data") multi_response, error = yield self.application.process_api_data(project, data, is_owner_request) if error: raise tornado.web.HTTPError(400, log_message=error) if self.application.collector: self.application.collector.incr('api') timer.stop() self.json_response(multi_response.as_message())
def post(self, project_id): """ Handle API HTTP requests. """ if not self.request.body: raise tornado.web.HTTPError(400, log_message="empty request") if self.request.headers.get("Content-Type", "").startswith("application/json"): # handle JSON requests if corresponding Content-Type specified try: request_data = json_decode(self.request.body) except ValueError: raise tornado.web.HTTPError(400, log_message="malformed json") if not isinstance(request_data, dict): raise tornado.web.HTTPError(400, log_message="object expected") sign = request_data.get("sign") encoded_data = request_data.get("data") else: # handle application/x-www-form-urlencoded request sign = self.get_argument('sign', None) encoded_data = self.get_argument('data', None) if not sign: raise tornado.web.HTTPError(400, log_message="no data sign") if not encoded_data: raise tornado.web.HTTPError(400, log_message="no data") is_owner_request = False if project_id == self.application.OWNER_API_PROJECT_ID: # API request aims to be from superuser is_owner_request = True if is_owner_request: # use api secret key from configuration to check sign secret = self.application.config.get("api_secret") if not secret: raise tornado.web.HTTPError(501, log_message="no api_secret in configuration file") project = None else: project, error = yield self.application.structure.get_project_by_id(project_id) if error: raise tornado.web.HTTPError(500, log_message=str(error)) if not project: raise tornado.web.HTTPError(404, log_message="project not found") # use project secret key to validate sign secret = project['secret_key'] is_valid = auth.check_sign( secret, project_id, encoded_data, sign ) if not is_valid: raise tornado.web.HTTPError(401, log_message="unauthorized") data = auth.decode_data(encoded_data) if not data: raise tornado.web.HTTPError(400, log_message="malformed data") multi_response = MultiResponse() if isinstance(data, dict): # single object request response = yield self.process_object(data, project, is_owner_request) multi_response.add(response) elif isinstance(data, list): # multiple object request if len(data) > self.application.ADMIN_API_MESSAGE_LIMIT: raise tornado.web.HTTPError( 400, log_message="admin API message limit exceeded (received {0} messages)".format( len(data) ) ) for obj in data: response = yield self.process_object(obj, project, is_owner_request) multi_response.add(response) else: raise tornado.web.HTTPError(400, log_message="data not a list or dictionary") if self.application.collector: self.application.collector.incr('api') self.json_response(multi_response.as_message())
def handle_connect(self, params): """ Authenticate client's connection, initialize required variables in case of successful authentication. """ if self.application.collector: self.application.collector.incr('connect') self.application.collector.incr(self.sock.session.transport_name) if self.is_authenticated: raise Return((self.uid, None)) token = params["token"] user = params["user"] project_id = params["project"] timestamp = params["timestamp"] user_info = params.get("info") project, error = yield self.application.get_project(project_id) if error: raise Return((None, error)) secret_key = project['secret_key'] error_msg = self.validate_token(token, secret_key, project_id, user, timestamp, user_info) if error_msg: raise Return((None, error_msg)) if user_info is not None: try: user_info = json_decode(user_info) except Exception as err: logger.error("malformed JSON data in user_info") logger.error(err) user_info = None try: timestamp = int(timestamp) except ValueError: raise Return((None, "invalid timestamp")) self.user = user self.examined_at = timestamp if self.is_connection_must_be_checked(project, self.examined_at): # connection expired - this is a rare case when Centrifuge went offline # for a while or client turned on his computer from sleeping mode. # put this client into the queue of connections waiting for # permission to reconnect with expired credentials. To avoid waiting # client must reconnect with actual credentials i.e. reload browser # window. if project_id not in self.application.expired_reconnections: self.application.expired_reconnections[project_id] = [] self.application.expired_reconnections[project_id].append(self) if project_id not in self.application.expired_connections: self.application.expired_connections[project_id] = { "users": set(), "checked_at": None } self.application.expired_connections[project_id]["users"].add(user) self.connect_queue = toro.Queue(maxsize=1) value = yield self.connect_queue.get() if not value: yield self.close_sock() raise Return((None, self.application.UNAUTHORIZED)) else: self.connect_queue = None # Welcome to Centrifuge dear Connection! self.is_authenticated = True self.project_id = project_id self.token = token self.default_user_info = { 'user_id': self.user, 'client_id': self.uid, 'default_info': user_info, 'channel_info': None } self.channels = {} self.presence_ping_task = PeriodicCallback( self.send_presence_ping, self.application.engine.presence_ping_interval ) self.presence_ping_task.start() self.application.add_connection(project_id, self.user, self.uid, self) raise Return((self.uid, None))
def handle_connect(self, params): """ Authenticate client's connection, initialize required variables in case of successful authentication. """ if self.application.collector: self.application.collector.incr('connect') self.application.collector.incr(self.sock.session.transport_name) if self.is_authenticated: raise Return((self.uid, None)) project_id = params["project"] user = params["user"] info = params.get("info", "{}") if not self.application.INSECURE: token = params["token"] timestamp = params["timestamp"] else: token = timestamp = None project, error = yield self.application.get_project(project_id) if error: raise Return((None, error)) secret_key = project['secret_key'] if not self.application.INSECURE: error_msg = self.validate_token( token, secret_key, project_id, user, timestamp, info ) if error_msg: raise Return((None, error_msg)) if info: try: info = json_decode(info) except Exception as err: logger.error("malformed JSON data in user_info") logger.error(err) info = {} else: info = {} if not self.application.INSECURE: try: timestamp = int(timestamp) except ValueError: raise Return((None, "invalid timestamp")) else: # we are not interested in timestamp in case of insecure mode so just # set it to current timestamp timestamp = int(time.time()) self.user = user self.project_id = project_id self.timestamp = timestamp time_to_expire = None if not self.application.INSECURE and project.get('connection_check', False): now = time.time() time_to_expire = self.timestamp + project.get("connection_lifetime", 3600) - now if time_to_expire <= 0: raise Return(({"client": None, "expired": True, "ttl": project.get("connection_lifetime", 3600)}, None)) # Welcome to Centrifuge dear Connection! self.is_authenticated = True self.default_info = { 'user_id': self.user, 'client_id': self.uid, 'default_info': info, 'channel_info': None } self.channels = {} self.presence_ping_task = PeriodicCallback( self.send_presence_ping, self.application.engine.presence_ping_interval ) self.presence_ping_task.start() self.application.add_connection(project_id, self.user, self.uid, self) if time_to_expire: self.expire_timeout = IOLoop.current().add_timeout( time.time() + project.get("connection_lifetime", 3600), self.expire ) body = { "client": self.uid, "expired": False, "ttl": project.get("connection_lifetime", 3600) if project.get("connection_check", False) else None } raise Return((body, None))
def handle_connect(self, params): """ Authenticate client's connection, initialize required variables in case of successful authentication. """ if self.application.collector: self.application.collector.incr('connect') self.application.collector.incr(self.sock.session.transport_name) if self.is_authenticated: raise Return((self.uid, None)) project_name = params["project"] user = params["user"] info = params.get("info", "") if not self.application.INSECURE: token = params["token"] timestamp = params["timestamp"] else: token = timestamp = None project = self.application.get_project(project_name) if not project: raise Return((None, self.application.PROJECT_NOT_FOUND)) secret = project['secret'] if not self.application.INSECURE: error_msg = self.validate_token( token, secret, project_name, user, timestamp, info ) if error_msg: raise Return((None, error_msg)) if info: try: info = json_decode(info) except Exception as err: logger.error("malformed JSON data in user_info") logger.error(err) info = {} else: info = {} if not self.application.INSECURE: try: timestamp = int(timestamp) except ValueError: raise Return((None, "invalid timestamp")) else: # we are not interested in timestamp in case of insecure mode so just # set it to current timestamp timestamp = int(time.time()) self.user = user self.project_name = project_name self.timestamp = timestamp time_to_expire = None if not self.application.INSECURE and project['connection_lifetime'] > 0: now = time.time() conn_lifetime = project["connection_lifetime"] time_to_expire = self.timestamp + conn_lifetime - now if time_to_expire <= 0: raise Return(({"client": None, "expired": True, "ttl": conn_lifetime}, None)) # Welcome to Centrifuge dear Connection! self.is_authenticated = True self.default_info = { 'user': self.user, 'client': self.uid, 'default_info': info, 'channel_info': None } self.channels = {} self.presence_ping_task = PeriodicCallback( self.send_presence_ping, self.application.engine.presence_ping_interval ) self.presence_ping_task.start() self.application.add_connection(project_name, self.user, self.uid, self) conn_lifetime = project["connection_lifetime"] if time_to_expire: self.expire_timeout = IOLoop.current().add_timeout( time.time() + conn_lifetime, self.expire ) body = { "client": self.uid, "expired": False, "ttl": conn_lifetime if project["connection_lifetime"] > 0 else None } raise Return((body, None))
def handle_connect(self, params): """ Authenticate client's connection, initialize required variables in case of successful authentication. """ if self.application.collector: self.application.collector.incr('connect') self.application.collector.incr(self.sock.session.transport_name) if self.is_authenticated: raise Return((self.uid, None)) token = params["token"] user = params["user"] project_id = params["project"] timestamp = params["timestamp"] user_info = params.get("info") project, error = yield self.application.get_project(project_id) if error: raise Return((None, error)) secret_key = project['secret_key'] try: client_token = auth.get_client_token(secret_key, project_id, user, timestamp, user_info=user_info) except Exception as err: logger.error(err) raise Return((None, "invalid connection parameters")) if token != client_token: raise Return((None, "invalid token")) if user_info is not None: try: user_info = json_decode(user_info) except Exception as err: logger.error("malformed JSON data in user_info") logger.error(err) user_info = None try: timestamp = int(timestamp) except ValueError: raise Return((None, "invalid timestamp")) now = time.time() self.user = user self.examined_at = timestamp connection_check = project.get('connection_check', False) if connection_check and self.examined_at + project.get( "connection_lifetime", 24 * 365 * 3600) < now: # connection expired - this is a rare case when Centrifuge went offline # for a while or client turned on his computer from sleeping mode. # put this client into the queue of connections waiting for # permission to reconnect with expired credentials. To avoid waiting # client must reconnect with actual credentials i.e. reload browser # window. if project_id not in self.application.expired_reconnections: self.application.expired_reconnections[project_id] = [] self.application.expired_reconnections[project_id].append(self) if project_id not in self.application.expired_connections: self.application.expired_connections[project_id] = { "users": set(), "checked_at": None } self.application.expired_connections[project_id]["users"].add(user) self.connect_queue = toro.Queue(maxsize=1) value = yield self.connect_queue.get() if not value: yield self.close_sock() raise Return((None, self.application.UNAUTHORIZED)) else: self.connect_queue = None # Welcome to Centrifuge dear Connection! self.is_authenticated = True self.project_id = project_id self.token = token self.default_user_info = { 'user_id': self.user, 'client_id': self.uid, 'default_info': user_info, 'channel_info': None } self.channels = {} self.presence_ping_task = PeriodicCallback( self.send_presence_ping, self.application.engine.presence_ping_interval) self.presence_ping_task.start() self.application.add_connection(project_id, self.user, self.uid, self) raise Return((self.uid, None))