Exemple #1
0
    def check_users(project, users, timeout=5):

        address = project.get("connection_check_address")
        if not address:
            logger.debug("no connection check address for project {0}".format(project['name']))
            raise Return(())

        http_client = AsyncHTTPClient()
        request = HTTPRequest(
            address,
            method="POST",
            body=urlencode({
                'users': json.dumps(list(users))
            }),
            request_timeout=timeout
        )

        try:
            response = yield http_client.fetch(request)
        except Exception as err:
            logger.error(err)
            raise Return((None, None))
        else:
            if response.code != 200:
                raise Return((None, None))

            try:
                content = [str(x) for x in json.loads(response.body)]
            except Exception as err:
                logger.error(err)
                raise Return((None, err))

            raise Return((content, None))
Exemple #2
0
    def collect_expired_connections(self):
        """
        Find all expired connections in projects to check them later.
        """
        projects, error = yield self.structure.project_list()
        if error:
            logger.error(error)
            raise Return((None, error))

        for project in projects:

            project_id = project['_id']
            expired_connections, error = yield self.collect_project_expired_connections(project)
            if error:
                logger.error(error)
                continue

            if project_id not in self.expired_connections:
                self.expired_connections[project_id] = {
                    "users": set(),
                    "checked_at": None
                }

            current_expired_connections = self.expired_connections[project_id]["users"]
            self.expired_connections[project_id]["users"] = current_expired_connections | expired_connections

        raise Return((True, None))
Exemple #3
0
    def check_expired_connections(self):
        """
        For each project ask web application about users whose connections expired.
        Close connections of deactivated users and keep valid users' connections.
        """
        projects, error = yield self.structure.project_list()
        if error:
            raise Return((None, error))

        checks = []
        for project in projects:
            if project.get('connection_check', False):
                checks.append(self.check_project_expired_connections(project))

        try:
            # run all checks in parallel
            yield checks
        except Exception as err:
            logger.error(err)

        tornado.ioloop.IOLoop.instance().add_timeout(
            time.time() + self.CONNECTION_EXPIRE_CHECK_INTERVAL,
            self.check_expired_connections)

        raise Return((True, None))
Exemple #4
0
    def collect_expired_connections(self):
        """
        Find all expired connections in projects to check them later.
        """
        projects, error = yield self.structure.project_list()
        if error:
            logger.error(error)
            raise Return((None, error))

        for project in projects:

            project_id = project['_id']
            expired_connections, error = yield self.collect_project_expired_connections(
                project)
            if error:
                logger.error(error)
                continue

            if project_id not in self.expired_connections:
                self.expired_connections[project_id] = {
                    "users": set(),
                    "checked_at": None
                }

            current_expired_connections = self.expired_connections[project_id][
                "users"]
            self.expired_connections[project_id][
                "users"] = current_expired_connections | expired_connections

        raise Return((True, None))
Exemple #5
0
    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
        }))
Exemple #6
0
    def check_users(project, users, timeout=5):

        address = project.get("connection_check_address")
        if not address:
            logger.debug("no connection check address for project {0}".format(
                project['name']))
            raise Return(())

        http_client = AsyncHTTPClient()
        request = HTTPRequest(address,
                              method="POST",
                              body=urlencode(
                                  {'users': json.dumps(list(users))}),
                              request_timeout=timeout)

        try:
            response = yield http_client.fetch(request)
        except Exception as err:
            logger.error(err)
            raise Return((None, None))
        else:
            if response.code != 200:
                raise Return((None, None))

            try:
                content = [str(x) for x in json.loads(response.body)]
            except Exception as err:
                logger.error(err)
                raise Return((None, err))

            raise Return((content, None))
Exemple #7
0
    def process_publish(self, project, params, allowed_namespaces=None, client=None):
        """
        Publish message into appropriate channel.
        """
        if allowed_namespaces is None:
            project_namespaces, error = yield self.structure.get_project_namespaces(project)
            if error:
                raise Return((None, error))

            allowed_namespaces = dict((x['name'], x) for x in project_namespaces)

        message, error = yield self.prepare_message(
            project, allowed_namespaces, params, client
        )
        if error:
            raise Return((None, error))

        if isinstance(message, dict):
            # event prepared for publishing
            result, error = yield self.publish_message(
                message, allowed_namespaces
            )
            if error:
                raise Return((None, error))

            for callback in self.post_publish_callbacks:
                result, error = yield callback(message)
                if error:
                    logger.error(str(error))
        else:
            # message is error description
            raise Return((None, message))

        raise Return((True, None))
Exemple #8
0
    def process_publish(self, project, params, allowed_namespaces=None, client_id=None):
        """
        Publish message into appropriate channel.
        """
        if allowed_namespaces is None:
            project_namespaces, error = yield self.structure.get_project_namespaces(project)
            if error:
                raise Return((None, error))

            allowed_namespaces = dict((x['name'], x) for x in project_namespaces)

        message, error = yield self.prepare_message(
            project, allowed_namespaces, params, client_id
        )
        if error:
            raise Return((None, error))

        if isinstance(message, dict):
            # event prepared for publishing
            result, error = yield self.publish_message(
                message, allowed_namespaces
            )
            if error:
                raise Return((None, error))

            for callback in self.post_publish_callbacks:
                result, error = yield callback(message)
                if error:
                    logger.error(str(error))
        else:
            # message is error description
            raise Return((None, message))

        raise Return((True, None))
Exemple #9
0
 def update_structure_because_of_inconsistency(self):
     # try to update structure at least after a second
     logger.error("structure inconsistent, will try to update after {0} milliseconds".format(
         self.recover_interval
     ))
     self.structure_recover.stop()
     yield self.update()
Exemple #10
0
    def check_expired_connections(self):
        """
        For each project ask web application about users whose connections expired.
        Close connections of deactivated users and keep valid users' connections.
        """
        projects, error = yield self.structure.project_list()
        if error:
            raise Return((None, error))

        checks = []
        for project in projects:
            if project.get('connection_check', False):
                checks.append(self.check_project_expired_connections(project))

        try:
            # run all checks in parallel
            yield checks
        except Exception as err:
            logger.error(err)

        tornado.ioloop.IOLoop.instance().add_timeout(
            time.time()+self.CONNECTION_EXPIRE_CHECK_INTERVAL,
            self.check_expired_connections
        )

        raise Return((True, None))
Exemple #11
0
    def connect(self):
        """
        Connect from scratch
        """
        try:
            self.subscriber.connect(host=self.host, port=self.port)
            self.publisher.connect(host=self.host, port=self.port)
            self.worker.connect(host=self.host, port=self.port)
            if self.options.redis_api:
                self.listener.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.password:
                self.subscriber.auth(self.password, callback=self.on_auth)
                self.publisher.auth(self.password, callback=self.on_auth)
                self.worker.auth(self.password, callback=self.on_auth)
                if self.options.redis_api:
                    self.listener.auth(self.password, callback=self.on_auth)

            self.subscriber.select(self.db, callback=self.on_subscriber_select)
            self.publisher.select(self.db, callback=self.on_select)
            self.worker.select(self.db, callback=self.on_select)
            if self.options.redis_api:
                self.listener.select(self.db, callback=self.on_listener_select)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #12
0
    def on_listener_select(self, res):
        if res != self.OK_RESPONSE:
            # error returned
            logger.error("select database failed: {0}".format(res))
            self._need_reconnect = True
            return

        self.process_api_messages()
Exemple #13
0
 def _publish(self, channel, message):
     try:
         self.publisher.publish(channel, message)
     except StreamClosedError as e:
         self._need_reconnect = True
         logger.error(e)
         return False
     else:
         return True
Exemple #14
0
 def _publish(self, channel, message):
     try:
         self.publisher.publish(channel, message)
     except StreamClosedError as e:
         self._need_reconnect = True
         logger.error(e)
         return False
     else:
         return True
    def process_object(self, obj, project, is_owner_request):

        response = Response()

        try:
            validate(obj, req_schema)
        except ValidationError as e:
            response.error = str(e)
            raise Return(response)

        req_id = obj.get("uid", None)
        method = obj.get("method")
        params = obj.get("params")

        response.uid = req_id
        response.method = method

        schema = server_api_schema

        if is_owner_request and self.application.OWNER_API_PROJECT_PARAM in params:

            project_id = params[self.application.OWNER_API_PROJECT_PARAM]

            project, error = yield self.application.structure.get_project_by_id(
                project_id
            )
            if error:
                logger.error(error)
                response.error = self.application.INTERNAL_SERVER_ERROR
            if not project:
                response.error = self.application.PROJECT_NOT_FOUND

        try:
            params.pop(self.application.OWNER_API_PROJECT_PARAM)
        except KeyError:
            pass

        if not is_owner_request and method in owner_api_methods:
            response.error = self.application.PERMISSION_DENIED

        if not response.error:
            if method not in schema:
                response.error = self.application.METHOD_NOT_FOUND
            else:
                try:
                    validate(params, schema[method])
                except ValidationError as e:
                    response.error = str(e)
                else:
                    result, error = yield self.application.process_call(
                        project, method, params
                    )
                    response.body = result
                    response.error = error

        raise Return(response)
Exemple #16
0
    def process_api_object(self, obj, project, is_owner_request):

        response = Response()

        try:
            validate(obj, req_schema)
        except ValidationError as e:
            response.error = str(e)
            raise Return(response)

        req_id = obj.get("uid", None)
        method = obj.get("method")
        params = obj.get("params")

        response.uid = req_id
        response.method = method

        schema = server_api_schema

        if is_owner_request and self.OWNER_API_PROJECT_PARAM in params:

            project_id = params[self.OWNER_API_PROJECT_PARAM]

            project, error = yield self.structure.get_project_by_id(
                project_id
            )
            if error:
                logger.error(error)
                response.error = self.INTERNAL_SERVER_ERROR
            if not project:
                response.error = self.PROJECT_NOT_FOUND

        try:
            params.pop(self.OWNER_API_PROJECT_PARAM)
        except KeyError:
            pass

        if not is_owner_request and method in owner_api_methods:
            response.error = self.PERMISSION_DENIED

        if not response.error:
            if method not in schema:
                response.error = self.METHOD_NOT_FOUND
            else:
                try:
                    validate(params, schema[method])
                except ValidationError as e:
                    response.error = str(e)
                else:
                    result, error = yield self.process_call(
                        project, method, params
                    )
                    response.body = result
                    response.error = error

        raise Return(response)
Exemple #17
0
 def publish(self, channel, message, method=None):
     """
     Publish message into channel of stream.
     """
     method = method or self.DEFAULT_PUBLISH_METHOD
     message["message_type"] = method
     to_publish = json_encode(message)
     try:
         self.publisher.publish(channel, to_publish)
     except StreamClosedError as e:
         self._need_reconnect = True
         logger.error(e)
Exemple #18
0
 def publish(self, channel, message, method=None):
     """
     Publish message into channel of stream.
     """
     method = method or self.DEFAULT_PUBLISH_METHOD
     message["message_type"] = method
     to_publish = json_encode(message)
     try:
         self.publisher.publish(channel, to_publish)
     except StreamClosedError as e:
         self._need_reconnect = True
         logger.error(e)
Exemple #19
0
    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 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
Exemple #21
0
    def update_channel_user_info(self, body, namespace_name, 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.setdefault(namespace_name, {})
        self.channel_user_info[namespace_name][channel] = info
    def validate_token(token, secret_key, project_id, user, timestamp, user_info):
        try:
            is_valid_token = auth.check_client_token(
                token, secret_key, project_id, user, timestamp, user_info=user_info
            )
        except Exception as err:
            logger.error(err)
            return "invalid connection parameters"

        if not is_valid_token:
            return "invalid token"

        return None
Exemple #23
0
    def validate_token(token, secret_key, project_id, user, timestamp, user_info):
        try:
            is_valid_token = auth.check_client_token(
                token, secret_key, project_id, user, timestamp, user_info=user_info
            )
        except Exception as err:
            logger.error(err)
            return "invalid connection parameters"

        if not is_valid_token:
            return "invalid token"

        return None
Exemple #24
0
    def connect(self):
        """
        Connect from scratch if connection not established.
        """
        subscriber_connect = False
        publisher_connect = False
        worker_connect = False
        listener_connect = False
        try:
            if not self.subscriber.is_connected():
                subscriber_connect = True
                self.subscriber.connect(host=self.host, port=self.port)
            if not self.publisher.is_connected():
                publisher_connect = True
                self.publisher.connect(host=self.host, port=self.port)
            if not self.worker.is_connected():
                worker_connect = True
                self.worker.connect(host=self.host, port=self.port)
            if self.options.redis_api:
                if not self.listener.is_connected():
                    listener_connect = True
                    self.listener.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.password:
                if subscriber_connect:
                    self.subscriber.auth(self.password, callback=self.on_auth)
                if publisher_connect:
                    self.publisher.auth(self.password, callback=self.on_auth)
                if worker_connect:
                    self.worker.auth(self.password, callback=self.on_auth)
                if self.options.redis_api:
                    if listener_connect:
                        self.listener.auth(self.password, callback=self.on_auth)

            if subscriber_connect:
                self.subscriber.select(self.db, callback=self.on_subscriber_select)
            if publisher_connect:
                self.publisher.select(self.db, callback=self.on_select)
            if worker_connect:
                self.worker.select(self.db, callback=self.on_select)
            if self.options.redis_api:
                if listener_connect:
                    self.listener.select(self.db, callback=self.on_listener_select)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #25
0
    def on_subscriber_select(self, res):
        """
        After selecting subscriber database subscribe on channels
        """
        if res != self.OK_RESPONSE:
            # error returned
            logger.error("pubsub select database for subscriber: {0}".format(res))
            return

        self.subscriber.subscribe(CONTROL_CHANNEL, callback=self.dispatch_published_message)
        self.subscriber.subscribe(ADMIN_CHANNEL, callback=self.dispatch_published_message)

        for subscription in self.subscriptions.copy():
            if subscription not in self.subscriptions:
                continue
            self.subscriber.subscribe(subscription, callback=self.dispatch_published_message)
    def close_sock(self, pause=True, pause_value=1):
        """
        Force closing connection.
        """
        if pause:
            # sleep for a while before closing connection to prevent mass invalid reconnects
            yield sleep(pause_value)

        try:
            if self.sock:
                self.sock.close()
            else:
                yield self.close()
        except Exception as err:
            logger.error(err)
        raise Return((True, None))
Exemple #27
0
    def close_sock(self, pause=True, pause_value=1):
        """
        Force closing connection.
        """
        if pause:
            # sleep for a while before closing connection to prevent mass invalid reconnects
            yield sleep(pause_value)

        try:
            if self.sock:
                self.sock.close()
            else:
                yield self.close()
        except Exception as err:
            logger.error(err)
        raise Return((True, None))
Exemple #28
0
    def on_subscriber_select(self, res):
        """
        After selecting subscriber database subscribe on channels
        """
        if res != self.OK_RESPONSE:
            # error returned
            logger.error("select database failed: {0}".format(res))
            self._need_reconnect = True
            return

        self.subscriber.subscribe(self.admin_channel_name, callback=self.on_redis_message)
        self.subscriber.subscribe(self.control_channel_name, callback=self.on_redis_message)

        for subscription in self.subscriptions.copy():
            if subscription not in self.subscriptions:
                continue
            self.subscriber.subscribe(subscription, callback=self.on_redis_message)
Exemple #29
0
    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}))
Exemple #30
0
    def update_user_info(self, body, namespace_name, channel):
        """
        Try to extract user info from response body and remember it
        for namespace and channel.
        """
        try:
            info = json_decode(body)
        except Exception as e:
            logger.error(str(e))
            info = {}

        user_info = {
            'user_id': self.user,
            'client_id': self.uid,
            'data': info
        }
        self.user_info.setdefault(namespace_name, {})
        self.user_info[namespace_name][channel] = json_encode(user_info)
Exemple #31
0
    def connect(self):
        """
        Connect to Redis.
        Do not even try to connect if State is faked.
        """
        if self.fake:
            return

        try:
            self.client.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.db and isinstance(self.db, int):
                self.client.select(self.db, callback=self.on_select)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #32
0
    def connect(self):
        """
        Connect to Redis.
        Do not even try to connect if State is faked.
        """
        if self.fake:
            return

        try:
            self.client.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.db and isinstance(self.db, int):
                self.client.select(self.db, callback=self.on_select)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #33
0
    def connect(self):
        """
        Connect from scratch, resubscribe on channels etc
        """
        try:
            self.subscriber.connect(host=self.host, port=self.port)
            self.publisher.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.db:
                self.subscriber.select(self.db, callback=self.on_subscriber_select)
                self.publisher.select(self.db, callback=self.on_publisher_select)
            else:
                self.on_subscriber_select(self.OK_RESPONSE)
                self.on_publisher_select(self.OK_RESPONSE)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #34
0
    def on_subscriber_select(self, res):
        """
        After selecting subscriber database subscribe on channels
        """
        if res != self.OK_RESPONSE:
            # error returned
            logger.error("select database for subscriber: {0}".format(res))
            return

        self.subscriber.subscribe(CONTROL_CHANNEL,
                                  callback=self.dispatch_published_message)
        self.subscriber.subscribe(ADMIN_CHANNEL,
                                  callback=self.dispatch_published_message)

        for subscription in self.subscriptions.copy():
            if subscription not in self.subscriptions:
                continue
            self.subscriber.subscribe(subscription,
                                      callback=self.dispatch_published_message)
Exemple #35
0
    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
        }))
Exemple #36
0
    def connect(self):
        """
        Connect from scratch, resubscribe on channels etc
        """
        try:
            self.subscriber.connect(host=self.host, port=self.port)
            self.publisher.connect(host=self.host, port=self.port)
        except Exception as e:
            logger.error("error connecting to Redis server: %s" % (str(e)))
        else:
            if self.db:
                self.subscriber.select(self.db,
                                       callback=self.on_subscriber_select)
                self.publisher.select(self.db,
                                      callback=self.on_publisher_select)
            else:
                self.on_subscriber_select(self.OK_RESPONSE)
                self.on_publisher_select(self.OK_RESPONSE)

        self.connection_check.stop()
        self.connection_check.start()
Exemple #37
0
    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 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))
Exemple #39
0
 def on_error(self, error):
     logger.error(str(error))
     self._CONSISTENT = False
     raise Return((None, error))
Exemple #40
0
 def on_error(self, error):
     logger.error(str(error))
     self._consistent = False
     raise Return((None, error))
Exemple #41
0
 def on_select(self, res):
     if res != "OK":
         logger.error("state select database: {0}".format(res))
Exemple #42
0
def on_error(error):
    """
    General error wrapper.
    """
    logger.error(str(error))
    raise Return((None, error))
Exemple #43
0
 def on_select(self, res):
     if res != self.OK_RESPONSE:
         logger.error("select database failed: {0}".format(res))
         self._need_reconnect = True
Exemple #44
0
def stop_running(msg):
    """
    Called only during initialization when critical error occurred.
    """
    logger.error(msg)
    sys.exit(1)
Exemple #45
0
def on_error(error):
    """
    General error wrapper.
    """
    logger.error(str(error))
    raise Return((None, error))
Exemple #46
0
 def on_auth(self, res):
     if res != self.OK_RESPONSE:
         logger.error("auth failed: {0}".format(res))
Exemple #47
0
    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)
Exemple #48
0
    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))
Exemple #49
0
 def on_error(self, error):
     logger.error(error)
     self._consistent = False
     self.structure_recover.start()
     raise Return((None, error))
Exemple #50
0
    def post(self, project_id):
        """
        Handle API HTTP requests.
        """
        if not self.request.body:
            raise tornado.web.HTTPError(400, log_message="empty request")

        sign = self.get_argument('sign', None)

        if not sign:
            raise tornado.web.HTTPError(400, log_message="no data sign")

        encoded_data = self.get_argument('data', None)
        if not encoded_data:
            raise tornado.web.HTTPError(400, log_message="no data")

        is_owner_request = False

        if project_id == self.application.MAGIC_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.settings["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")

        response = Response()

        try:
            validate(data, req_schema)
        except ValidationError as e:
            response.error = str(e)
        else:
            req_id = data.get("uid", None)
            method = data.get("method")
            params = data.get("params")

            response.uid = req_id
            response.method = method

            schema = server_api_schema

            if is_owner_request and self.application.MAGIC_PROJECT_PARAM in params:

                project_id = params[self.application.MAGIC_PROJECT_PARAM]

                project, error = yield self.application.structure.get_project_by_id(
                    project_id)
                if error:
                    logger.error(error)
                    response.error = self.application.INTERNAL_SERVER_ERROR
                if not project:
                    response.error = self.application.PROJECT_NOT_FOUND

            try:
                params.pop(self.application.MAGIC_PROJECT_PARAM)
            except KeyError:
                pass

            if not is_owner_request and method in owner_api_methods:
                response.error = self.application.PERMISSION_DENIED

            if not response.error:
                if method not in schema:
                    response.error = self.application.METHOD_NOT_FOUND
                else:
                    try:
                        validate(params, schema[method])
                    except ValidationError as e:
                        response.error = str(e)
                    else:
                        result, error = yield self.application.process_call(
                            project, method, params)
                        response.body = result
                        response.error = error

        self.json_response(response.as_message())
    def authorize(self, auth_address, project, channel):
        """
        Send POST request to web application to ask it if current client
        has a permission to subscribe on channel.
        """
        project_id = self.project_id

        http_client = AsyncHTTPClient()
        request = HTTPRequest(
            auth_address,
            method="POST",
            body=urlencode({
                'user': self.user,
                'channel': channel
            }),
            request_timeout=1
        )

        max_auth_attempts = project.get('max_auth_attempts')
        back_off_interval = project.get('back_off_interval')
        back_off_max_timeout = project.get('back_off_max_timeout')

        attempts = 0

        while attempts < max_auth_attempts:

            # get current timeout for project
            current_attempts = self.application.back_off.setdefault(project_id, 0)

            factor = random.randint(0, 2**current_attempts-1)
            timeout = factor*back_off_interval

            if timeout > back_off_max_timeout:
                timeout = back_off_max_timeout

            # wait before next authorization request attempt
            yield sleep(float(timeout)/1000)

            try:
                response = yield http_client.fetch(request)
            except HTTPError as err:
                if err.code == 403:
                    # access denied for this client
                    raise Return((False, None))
                else:
                    # let it fail and try again after some timeout
                    logger.info("{0} status code when fetching auth address {1}".format(
                        err.code, auth_address
                    ))
            except Exception as err:
                logger.error("error fetching auth address {0}".format(auth_address))
                logger.exception(err)
                raise Return((False, None))
            else:
                # reset back-off attempts
                self.application.back_off[project_id] = 0

                if response.code == 200:
                    # auth successful
                    self.update_channel_user_info(response.body, channel)
                    raise Return((True, None))

                else:
                    # access denied for this client
                    raise Return((False, None))
            attempts += 1
            self.application.back_off[project_id] += 1

        raise Return((False, None))
Exemple #52
0
 def on_select(self, res):
     if res != self.OK_RESPONSE:
         logger.error("state select database: {0}".format(res))
    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))
Exemple #54
0
    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))
Exemple #55
0
def stop_running(msg):
    """
    Called only during initialization when critical error occurred.
    """
    logger.error(msg)
    sys.exit(1)
Exemple #56
0
    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))