Example #1
0
    def __init__(self, handler):
        _path = msg.join_path(self._path, '__init__')

        self.handler = handler
        self.pub_subs = {
            'w': self.handler.ws_pub_sub,
            'd': mb,
            'l': self.handler.local_pub_sub,
        }

        for attr_name in dir(self):
            attribute = getattr(self, attr_name)
            if hasattr(attribute, 'msg_types'):
                for _type, channels in attribute.msg_types:
                    msg.code_debug(
                        _path,
                        'Adding action: %r ...' % attribute
                    )
                    self.register_action_in(
                        msg_type=_type, action=attribute,
                        channels=channels)

        finalize(
            self, msg.code_debug, self._path,
            'Deleting WSClass {0} from {0.handler} '
            '...'.format(self)
        )
Example #2
0
    def send_message(self, message):
        """Send a message

        ``self.send_function`` is used if it was specified
        during object creation. If not,
        ``self.execute_actions`` is used.

        :param dict message: The message to be sent.

        :raises MsgIsNotDictError:
            If ``message`` is not a dictionary.

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If ``self.send_function`` wasn't specified
            during object creation and there's no
            registered action for this message type.
        """
        _path = msg.join_path(self._path, 'send_message')

        if not isinstance(message, dict):
            raise MsgIsNotDictError(message)

        if 'type' not in message:
            raise NoMessageTypeError(message)

        msg.code_debug(_path, 'Sending message: {}.'.format(message))

        if self.send_function is None:
            self.execute_actions(message)
        else:
            IOLoop.current().spawn_callback(self.send_function, message)
Example #3
0
    def end(self):
        """Clean up the associated objects

        This coroutine calls :meth:`src.wsclass.WSClass.end`
        for all objects in ``self.ws_objects`` and it
        removes ``self`` from ``self.__class__.clients``.

        This coroutine is setup to be called when
        the WebSocket connection closes or when the program
        ends.
        """
        try:
            exceptions = []
            for ws_object in self.ws_objects.values():
                try:
                    yield ws_object.end()
                except:
                    exceptions.append(exc_info())

            for exception in exceptions:
                print_exception(*exception)

            self.__class__.clients.discard(self)

            msg.code_debug(
                msg.join_path(__name__, self.end.__qualname__),
                'Connection closed! {0} '
                '({0.request.remote_ip})'.format(self))

        except:
            raise
Example #4
0
    def end_room_usage(self, user, is_teacher, is_student):
        try:
            room_code = self.handler.room_code
            room = yield room_code.room

            # Room Deassign Course
            if is_teacher and \
                    user.course_id is not None and \
                    user.instances == 1:
                yield room.deassign_course(user.course_id)

            # Leave seat
            if is_student and not self.dont_leave_seat:
                yield room.leave_seat(room_code.seat_id)

        except AttributeError:
            if not hasattr(self.handler, 'room_code') or \
                    self.handler.room_code is None:
                msg.code_warning(
                    msg.join_path(__name__, self.end.__qualname__),
                    "room_code wasn't initialized at "
                    "{.__class__.__name__}'s "
                    "end.".format(self))

            else:
                raise
Example #5
0
    def end_room_usage(self, user, is_teacher, is_student):
        try:
            room_code = self.handler.room_code
            room = yield room_code.room

            # Room Deassign Course
            if is_teacher and \
                    user.course_id is not None and \
                    user.instances == 1:
                yield room.deassign_course(user.course_id)

            # Leave seat
            if is_student and not self.dont_leave_seat:
                yield room.leave_seat(room_code.seat_id)

        except AttributeError:
            if not hasattr(self.handler, 'room_code') or \
                    self.handler.room_code is None:
                msg.code_warning(
                    msg.join_path(
                        __name__, self.end.__qualname__),
                    "room_code wasn't initialized at "
                    "{.__class__.__name__}'s "
                    "end.".format(self)
                )

            else:
                raise
Example #6
0
    def request_disc_doc(self):
        _path = msg.join_path(self._path, "request_disc_doc")

        def _req_disc_doc():
            dd = self.disc_doc_client.request("https://accounts.google.com/.well-known/" "openid-configuration", "GET")
            return self.decode_httplib2_json(dd)

        if self.disc_doc is None:
            msg.code_debug(_path, "Requesting discovery document ...")

            with ThreadPoolExecutor(1) as thread:
                self.__class__.disc_doc = thread.submit(_req_disc_doc)
                self.disc_doc = yield self.__class__.disc_doc
                # Este yield tiene que ir dentro, ya que el
                # thread no se comenzará a ejecutar si no se
                # yieldea y no se puede comenzar a ejecutar
                # fuera del with ya que ahí no existe ... o
                # algo asi XD :C
                self.__class__.disc_doc = None
                msg.code_debug(_path, "self.__class__.disc_doc = None")

            msg.code_debug(_path, "Discovery document arrived!")

        else:
            msg.code_debug(_path, "Waiting for discovery document ...")

            self.disc_doc = yield self.disc_doc
            msg.code_debug(_path, "Got the discovery document!")
Example #7
0
    def request_disc_doc(self):
        _path = msg.join_path(self._path, 'request_disc_doc')

        def _req_disc_doc():
            dd = self.disc_doc_client.request(
                'https://accounts.google.com/.well-known/'
                'openid-configuration', 'GET')
            return self.decode_httplib2_json(dd)

        if self.disc_doc is None:
            msg.code_debug(_path, 'Requesting discovery document ...')

            with ThreadPoolExecutor(1) as thread:
                self.__class__.disc_doc = thread.submit(_req_disc_doc)
                self.disc_doc = \
                    yield self.__class__.disc_doc
                # Este yield tiene que ir dentro, ya que el
                # thread no se comenzará a ejecutar si no se
                # yieldea y no se puede comenzar a ejecutar
                # fuera del with ya que ahí no existe ... o
                # algo asi XD :C
                self.__class__.disc_doc = None
                msg.code_debug(_path, 'self.__class__.disc_doc = None')

            msg.code_debug(_path, 'Discovery document arrived!')

        else:
            msg.code_debug(_path, 'Waiting for discovery document ...')

            self.disc_doc = yield self.disc_doc
            msg.code_debug(_path, 'Got the discovery document!')
Example #8
0
    def __init__(self, handler):
        _path = msg.join_path(self._path, '__init__')

        self.handler = handler
        self.pub_subs = {
            'w': self.handler.ws_pub_sub,
            'd': mb,
            'l': self.handler.local_pub_sub,
        }

        for attr_name in dir(self):
            attribute = getattr(self, attr_name)
            if hasattr(attribute, 'msg_types'):
                for _type, channels in attribute.msg_types:
                    msg.code_debug(
                        _path,
                        'Adding action: %r ...' % attribute
                    )
                    self.register_action_in(
                        msg_type=_type, action=attribute,
                        channels=channels)

        finalize(
            self, msg.code_debug, self._path,
            'Deleting WSClass {0} from {0.handler} '
            '...'.format(self)
        )
    def end(self):
        """Clean up the associated objects

        This coroutine calls :meth:`src.wsclass.WSClass.end`
        for all objects in ``self.ws_objects`` and it
        removes ``self`` from ``self.__class__.clients``.

        This coroutine is setup to be called when
        the WebSocket connection closes or when the program
        ends.
        """
        try:
            exceptions = []
            for ws_object in self.ws_objects.values():
                try:
                    yield ws_object.end()
                except:
                    exceptions.append(
                        exc_info()
                    )

            for exception in exceptions:
                print_exception(*exception)

            self.__class__.clients.discard(self)

            msg.code_debug(
                msg.join_path(
                    __name__, self.end.__qualname__),
                'Connection closed! {0} '
                '({0.request.remote_ip})'.format(self)
            )

        except:
            raise
Example #10
0
    def get(self):
        _path = msg.join_path(self._path, 'get')
        try:
            redirect_uri = urlunparse((self.get_scheme(), self.request.host,
                                       conf.login_path, '', '', ''))
            # remember the user for a longer period of time
            remember = self.get_argument('remember', False)
            room_code = self.get_argument('room_code', False)
            state = jwt.encode({
                'remember': remember,
                'room_code': room_code
            }, secrets['simple'])
            flow = oa2_client.OAuth2WebServerFlow(
                google_secrets['web']['client_id'],
                google_secrets['web']['client_secret'],
                scope='openid profile',
                redirect_uri=redirect_uri,
                state=state)

            auth_code = self.get_argument('code', False)

            if not auth_code:
                auth_uri = flow.step1_get_authorize_url()
                self.redirect(auth_uri)

            else:
                with ThreadPoolExecutor(1) as thread:
                    credentials = yield thread.submit(flow.step2_exchange,
                                                      auth_code)
                # Intercambiar el codigo antes que nada para
                # evitar ataques

                yield self.request_disc_doc()

                userinfo_endpoint = \
                    self.disc_doc['userinfo_endpoint']

                http_auth = credentials.authorize(httplib2.Http())

                with ThreadPoolExecutor(1) as thread:
                    userinfo = yield thread.submit(http_auth.request,
                                                   userinfo_endpoint)
                userinfo = self.decode_httplib2_json(userinfo)
                # https://developers.google.com/+/api/
                # openidconnect/getOpenIdConnect

                user = yield db.User.from_google_userinfo(userinfo)
                token = jwt.encode({
                    'id': user.id,
                    'exp': self.get_exp()
                }, user.secret)
                msg.code_debug(_path, 'Rendering login.html ...')
                self.render('login.html', token=token)

        except oa2_client.FlowExchangeError:
            self.render('boxes.html',
                        classes={'system'},
                        critical='Error de autenticación!')
Example #11
0
    def create(cls, name, svg_path):
        try:
            _path = msg.join_path(cls._path, 'create')

            ElementTree.register_namespace('', NS['svg'])
            elem_tree = yield run_in_thread(
                ElementTree.parse, svg_path)

            seat_circles = elem_tree.iterfind(
                ".//svg:circle[@class='seat']", NS)
            seat_attributes = (c.attrib
                               for c in seat_circles)
            seats = {a['id']: {'used': False,
                               'x': a['cx'],
                               'y': a['cy']}
                     for a in seat_attributes}

            self = yield super().create(name)
            self.setattr(
                '_map', elem_tree.getroot()
            )
            yield self.store_dict(
                {
                    'map_source': ElementTree.tostring(
                        self.map, encoding="unicode"),
                    'seats': seats
                }
            )

            code = yield Code.create(
                room_id=self.id, code_type=CodeType.room)

            codes = [code]
            for seat in self.seats:
                code = yield Code.create(
                    room_id=self.id,
                    code_type=CodeType.seat,
                    seat_id=seat
                )
                codes.append(code)

            return (self, codes)

        except DuplicateKeyError:
            raise

        except OperationFailure:
            msg.obj_creation_error(_path, cls, name=name,
                                   svg_path=svg_path)
            cls.coll.remove(name)
            raise
Example #12
0
    def initialize(self):
        _path = msg.join_path(self._path, "initialize")
        msg.code_debug(_path, "New connection established! {0} " "({0.request.remote_ip})".format(self))

        self.local_pub_sub = OwnerPubSub(name="local_pub_sub")

        self.ws_pub_sub = OwnerPubSub(name="ws_pub_sub", send_function=self.write_message)
        self.ws_objects = {ws_class: ws_class(self) for ws_class in self.ws_classes}

        self.__class__.clients.add(self)
        self.__class__.client_count += 1

        self.clean_closed = False
        self.ping_timeout_handle = None
Example #13
0
class RouterWSC(src.wsclass.WSClass):
    _path = msg.join_path(_path, 'RouterWSC')

    @subscribe('toFrontend', channels={'d', 'l', 'w'})
    def to_frontend(self, message):
        self.redirect_content_to('w', message)

    @subscribe('toDatabase', channels={'l'})
    def to_database(self, message):
        self.redirect_content_to('d', message)

    @subscribe('toLocal', channels={'d'})
    def to_local(self, message):
        self.redirect_content_to('l', message)
Example #14
0
class OwnerPubSub(PubSub):
    # Used by inherited methods.
    _path = msg.join_path(_path, 'OwnerPubSub')

    def __init__(self, name='owner_pub_sub_instance', send_function=None):
        super().__init__(name, send_function)
        self.owners = {}

    def register(self, msg_type, action, owner=None):
        super().register(msg_type, action)

        if owner in self.owners:
            self.owners[owner].add((msg_type, action))
        else:
            self.owners[owner] = {(msg_type, action)}

    def remove_owner(self, owner):
        """Remove all actions registered by ``owner``.

        :param object owner:
            Object that owns a set of actions registeres in
            this PubSub object.

        :raises UnrecognizedOwnerError:
            If ``owner`` wasn't previously registered in
            this PubSubobject.
        """
        try:
            for msg_type, action in self.owners[owner]:
                self.remove(msg_type, action)

            del self.owners[owner]

        except NoActionForMsgTypeError:
            warn("This method tried to remove an action "
                 "that was registered by an owner, but now "
                 "isn't in ``self.actions``. This may be "
                 "caused because two owners registered the "
                 "same action. Please review your code. "
                 "This may be a source of errors.")

        except KeyError as ke:
            if owner not in self.owners:
                uoe = UnrecognizedOwnerError(
                    'Owner {owner} is not registered in '
                    'the {ps.name} PubSub '
                    'object.'.format(owner=owner, ps=self))
                raise uoe from ke
            else:
                raise
Example #15
0
    def create(cls, room_id, code_type, seat_id=None):
        """Create a new code.

        .. todo::
            *   review error handling.
        """
        _path = msg.join_path(cls._path, 'create')

        def creat_err_msg():
            msg.obj_creation_error(_path,
                                   cls,
                                   room_id=room_id,
                                   code_type=code_type,
                                   seat_id=seat_id)

        if not isinstance(code_type, CodeType):
            raise TypeError

        if seat_id is not None and \
                not isinstance(seat_id, str):
            raise TypeError

        if (code_type is CodeType.room) == \
                (seat_id is not None):
            raise ValueError

        try:
            # Throws NoObjectReturnedFromDB
            room = yield Room.get(room_id)

            self, code = yield cls._gen_new_code()
            self.setattr('_room', room)

            # Throws OperationFailure
            yield self.store_dict({
                'room_id': room_id,
                'code_type': code_type.value,
                'seat_id': seat_id
            })
            return self

        except NoObjectReturnedFromDB:
            creat_err_msg()
            raise OperationFailure

        except OperationFailure:
            creat_err_msg()
            cls.coll.remove(code)
            raise
Example #16
0
    def create(cls, room_id, code_type, seat_id=None):
        """Create a new code.

        .. todo::
            *   review error handling.
        """
        _path = msg.join_path(cls._path, 'create')

        def creat_err_msg():
            msg.obj_creation_error(_path, cls,
                                   room_id=room_id,
                                   code_type=code_type,
                                   seat_id=seat_id)

        if not isinstance(code_type, CodeType):
            raise TypeError

        if seat_id is not None and \
                not isinstance(seat_id, str):
            raise TypeError

        if (code_type is CodeType.room) == \
                (seat_id is not None):
            raise ValueError

        try:
            # Throws NoObjectReturnedFromDB
            room = yield Room.get(room_id)

            self, code = yield cls._gen_new_code()
            self.setattr('_room', room)

            # Throws OperationFailure
            yield self.store_dict(
                {'room_id': room_id,
                 'code_type': code_type.value,
                 'seat_id': seat_id})
            return self

        except NoObjectReturnedFromDB:
            creat_err_msg()
            raise OperationFailure

        except OperationFailure:
            creat_err_msg()
            cls.coll.remove(code)
            raise
Example #17
0
    def get(self):
        _path = msg.join_path(self._path, "get")
        try:
            redirect_uri = urlunparse((self.get_scheme(), self.request.host, conf.login_path, "", "", ""))
            # remember the user for a longer period of time
            remember = self.get_argument("remember", False)
            room_code = self.get_argument("room_code", False)
            state = jwt.encode({"remember": remember, "room_code": room_code}, secrets["simple"])
            flow = oa2_client.OAuth2WebServerFlow(
                google_secrets["web"]["client_id"],
                google_secrets["web"]["client_secret"],
                scope="openid profile",
                redirect_uri=redirect_uri,
                state=state,
            )

            auth_code = self.get_argument("code", False)

            if not auth_code:
                auth_uri = flow.step1_get_authorize_url()
                self.redirect(auth_uri)

            else:
                with ThreadPoolExecutor(1) as thread:
                    credentials = yield thread.submit(flow.step2_exchange, auth_code)
                # Intercambiar el codigo antes que nada para
                # evitar ataques

                yield self.request_disc_doc()

                userinfo_endpoint = self.disc_doc["userinfo_endpoint"]

                http_auth = credentials.authorize(httplib2.Http())

                with ThreadPoolExecutor(1) as thread:
                    userinfo = yield thread.submit(http_auth.request, userinfo_endpoint)
                userinfo = self.decode_httplib2_json(userinfo)
                # https://developers.google.com/+/api/
                # openidconnect/getOpenIdConnect

                user = yield db.User.from_google_userinfo(userinfo)
                token = jwt.encode({"id": user.id, "exp": self.get_exp()}, user.secret)
                msg.code_debug(_path, "Rendering login.html ...")
                self.render("login.html", token=token)

        except oa2_client.FlowExchangeError:
            self.render("boxes.html", classes={"system"}, critical="Error de autenticación!")
Example #18
0
    def create(cls, name, svg_path):
        try:
            _path = msg.join_path(cls._path, 'create')

            ElementTree.register_namespace('', NS['svg'])
            elem_tree = yield run_in_thread(ElementTree.parse, svg_path)

            seat_circles = elem_tree.iterfind(".//svg:circle[@class='seat']",
                                              NS)
            seat_attributes = (c.attrib for c in seat_circles)
            seats = {
                a['id']: {
                    'used': False,
                    'x': a['cx'],
                    'y': a['cy']
                }
                for a in seat_attributes
            }

            self = yield super().create(name)
            self.setattr('_map', elem_tree.getroot())
            yield self.store_dict({
                'map_source':
                ElementTree.tostring(self.map, encoding="unicode"),
                'seats':
                seats
            })

            code = yield Code.create(room_id=self.id, code_type=CodeType.room)

            codes = [code]
            for seat in self.seats:
                code = yield Code.create(room_id=self.id,
                                         code_type=CodeType.seat,
                                         seat_id=seat)
                codes.append(code)

            return (self, codes)

        except DuplicateKeyError:
            raise

        except OperationFailure:
            msg.obj_creation_error(_path, cls, name=name, svg_path=svg_path)
            cls.coll.remove(name)
            raise
Example #19
0
    def execute_actions(self, message):
        """Execute actions associated to the type of message

        :param dict message: The message to be sent

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If there's no registered action for this message
            type.

        :raises NotDictError:
            If ``message`` is not an instance of ``dict``.
        """
        _path = msg.join_path(self._path, 'execute_actions')
        msg.code_debug(
            _path,
            'Message arrived: {}.'.format(message)
        )

        try:
            for action in self.actions[message['type']]:
                IOLoop.current().spawn_callback(action,
                                                message)
        except TypeError as te:
            if not isinstance(message, dict):
                nde = NotDictError('message', message)
                raise nde from te
            else:
                raise

        except KeyError as ke:
            if 'type' not in message:
                raise NoMessageTypeError(message) from ke

            elif message['type'] not in self.actions:
                nafmte = NoActionForMsgTypeError(message,
                                                 self.name)
                raise nafmte from ke
            else:
                raise
Example #20
0
    def send_message(self, message):
        """Send a message

        ``self.send_function`` is used if it was specified
        during object creation. If not,
        ``self.execute_actions`` is used.

        The message will be sent on the next iteration of
        the IOLoop. If you need to send the message
        immediately use "self.send_function" directly.

        :param dict message: The message to be sent.

        :raises NotDictError:
            If ``message`` is not a dictionary.

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If ``self.send_function`` wasn't specified
            during object creation and there's no
            registered action for this message type.
        """
        _path = msg.join_path(self._path, 'send_message')

        if not isinstance(message, dict):
            raise NotDictError('message', message)

        if 'type' not in message:
            raise NoMessageTypeError(message)

        msg.code_debug(
            _path,
            'Sending message: {}.'.format(message)
        )

        if self.send_function is None:
            self.execute_actions(message)
        else:
            IOLoop.current().spawn_callback(
                self.send_function, message)
Example #21
0
    def initialize(self):
        _path = msg.join_path(self._path, 'initialize')
        msg.code_debug(
            _path, 'New connection established! {0} '
            '({0.request.remote_ip})'.format(self))

        self.local_pub_sub = OwnerPubSub(name='local_pub_sub')

        self.ws_pub_sub = OwnerPubSub(name='ws_pub_sub',
                                      send_function=self.write_message)
        self.ws_objects = {
            ws_class: ws_class(self)
            for ws_class in self.ws_classes
        }

        self.__class__.clients.add(self)
        self.__class__.client_count += 1

        self.clean_closed = False
        self.ping_timeout_handle = None
Example #22
0
    def execute_actions(self, message):
        """Execute actions associated to the type of message

        :param dict message: The message to be sent

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If there's no registered action for this message
            type.

        :raises NotDictError:
            If ``message`` is not an instance of ``dict``.
        """
        _path = msg.join_path(self._path, 'execute_actions')
        msg.code_debug(_path, 'Message arrived: {}.'.format(message))

        try:
            for action in self.actions[message['type']]:
                IOLoop.current().spawn_callback(action, message)
        except TypeError as te:
            if not isinstance(message, dict):
                nde = NotDictError('message', message)
                raise nde from te
            else:
                raise

        except KeyError as ke:
            if 'type' not in message:
                raise NoMessageTypeError(message) from ke

            elif message['type'] not in self.actions:
                nafmte = NoActionForMsgTypeError(message, self.name)
                raise nafmte from ke
            else:
                raise
Example #23
0
class Code(DBObject):
    """Interface to the ``codes`` collection.

    .. todo::
        * Review error handling.
    """

    coll = db.codes
    _path = msg.join_path(_path, 'Code')

    @classmethod
    @coroutine
    def _gen_new_code(cls):
        for i in range(200):
            try:
                code = random_word(5, ascii_lowercase + digits)
                self = yield super().create(code)
                return (self, code)

            except DuplicateKeyError:
                msg.try_new_id_after_dup_obj_in_db(cls.path + '.create')
        else:
            msg.exhausted_tries(cls.path + '.create')

    @classmethod
    @coroutine
    def create(cls, room_id, code_type, seat_id=None):
        """Create a new code.

        .. todo::
            *   review error handling.
        """
        _path = msg.join_path(cls._path, 'create')

        def creat_err_msg():
            msg.obj_creation_error(_path,
                                   cls,
                                   room_id=room_id,
                                   code_type=code_type,
                                   seat_id=seat_id)

        if not isinstance(code_type, CodeType):
            raise TypeError

        if seat_id is not None and \
                not isinstance(seat_id, str):
            raise TypeError

        if (code_type is CodeType.room) == \
                (seat_id is not None):
            raise ValueError

        try:
            # Throws NoObjectReturnedFromDB
            room = yield Room.get(room_id)

            self, code = yield cls._gen_new_code()
            self.setattr('_room', room)

            # Throws OperationFailure
            yield self.store_dict({
                'room_id': room_id,
                'code_type': code_type.value,
                'seat_id': seat_id
            })
            return self

        except NoObjectReturnedFromDB:
            creat_err_msg()
            raise OperationFailure

        except OperationFailure:
            creat_err_msg()
            cls.coll.remove(code)
            raise

    def __str__(self):
        return '<%s -> (%s, %s)>' % (self.id, self.room_id, self.seat_id)

    @property
    @coroutine
    def room(self):
        if not hasattr(self, '_room'):
            room = yield Room.get(self.room_id)
            self.setattr('_room', room)
        return self._room

    @property
    def code_type(self):
        return CodeType(self._data['code_type'])
Example #24
0
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from warnings import warn

from tornado.ioloop import IOLoop

from src import messages as msg
from src.exceptions import NotDictError

_path = msg.join_path("src", "pub_sub")


class PubSub(object):

    """Implement the PubSub pattern.

    This class is designed to implement the PubSub pattern,
    in a way that is compatible with Tornado's coroutines.

    .. automethod:: __init__
    """

    _path = msg.join_path(_path, "PubSub")

    def __init__(self, name="pub_sub_instance", send_function=None):
Example #25
0
class Room(DBObject):
    coll = db.rooms
    _path = msg.join_path(_path, 'Room')
    defaults = {}

    @classmethod
    @coroutine
    def create(cls, name, svg_path):
        try:
            _path = msg.join_path(cls._path, 'create')

            ElementTree.register_namespace('', NS['svg'])
            elem_tree = yield run_in_thread(ElementTree.parse, svg_path)

            seat_circles = elem_tree.iterfind(".//svg:circle[@class='seat']",
                                              NS)
            seat_attributes = (c.attrib for c in seat_circles)
            seats = {
                a['id']: {
                    'used': False,
                    'x': a['cx'],
                    'y': a['cy']
                }
                for a in seat_attributes
            }

            self = yield super().create(name)
            self.setattr('_map', elem_tree.getroot())
            yield self.store_dict({
                'map_source':
                ElementTree.tostring(self.map, encoding="unicode"),
                'seats':
                seats
            })

            code = yield Code.create(room_id=self.id, code_type=CodeType.room)

            codes = [code]
            for seat in self.seats:
                code = yield Code.create(room_id=self.id,
                                         code_type=CodeType.seat,
                                         seat_id=seat)
                codes.append(code)

            return (self, codes)

        except DuplicateKeyError:
            raise

        except OperationFailure:
            msg.obj_creation_error(_path, cls, name=name, svg_path=svg_path)
            cls.coll.remove(name)
            raise

    def __str__(self):
        return self.name

    @coroutine
    def assign_course(self, course_id):
        """Adds a course from the courses set.

        :raises ConditionNotMetError:
            If the document no longer exists in the
            database.
        """
        yield self.modify({'$addToSet': {'courses': course_id}})

    @coroutine
    def deassign_course(self, course_id):
        """Remove a course from the courses set.

        :raises ConditionNotMetError:
            If the document no longer exists in the
            database.
        """
        yield self.modify({'$pull': {'courses': course_id}})

    @coroutine
    def _modify_seat(self, seat_id, use):
        try:
            used = 'seats.{}.used'.format(seat_id)
            yield self.modify_if({used: not use}, {'$set': {used: use}})
        except ConditionNotMetError as e:
            cnme = ConditionNotMetError('The seat {} is{} used.'.format(
                seat_id, '' if use else ' not'))
            raise cnme from e

    @coroutine
    def use_seat(self, seat_id):
        yield self._modify_seat(seat_id, use=True)

    @coroutine
    def leave_seat(self, seat_id):
        yield self._modify_seat(seat_id, use=False)

    @property
    def name(self):
        return self.id

    @property
    def map(self):
        if not hasattr(self, '_map'):
            self.setattr('_map', ElementTree.fromstring(self.map_source))
        return self._map
Example #26
0
class LoginHandler(RequestHandler):
    _path = msg.join_path(_path, 'LoginHandler')
    disc_doc = None  # google's discovery document
    disc_doc_client = httplib2.Http('.disc_doc.cache')
    # https://developers.google.com/api-client-library/
    # python/guide/thread_safety

    @coroutine
    def get(self):
        _path = msg.join_path(self._path, 'get')
        try:
            redirect_uri = urlunparse((self.get_scheme(), self.request.host,
                                       conf.login_path, '', '', ''))
            # remember the user for a longer period of time
            remember = self.get_argument('remember', False)
            room_code = self.get_argument('room_code', False)
            state = jwt.encode({
                'remember': remember,
                'room_code': room_code
            }, secrets['simple'])
            flow = oa2_client.OAuth2WebServerFlow(
                google_secrets['web']['client_id'],
                google_secrets['web']['client_secret'],
                scope='openid profile',
                redirect_uri=redirect_uri,
                state=state)

            auth_code = self.get_argument('code', False)

            if not auth_code:
                auth_uri = flow.step1_get_authorize_url()
                self.redirect(auth_uri)

            else:
                with ThreadPoolExecutor(1) as thread:
                    credentials = yield thread.submit(flow.step2_exchange,
                                                      auth_code)
                # Intercambiar el codigo antes que nada para
                # evitar ataques

                yield self.request_disc_doc()

                userinfo_endpoint = \
                    self.disc_doc['userinfo_endpoint']

                http_auth = credentials.authorize(httplib2.Http())

                with ThreadPoolExecutor(1) as thread:
                    userinfo = yield thread.submit(http_auth.request,
                                                   userinfo_endpoint)
                userinfo = self.decode_httplib2_json(userinfo)
                # https://developers.google.com/+/api/
                # openidconnect/getOpenIdConnect

                user = yield db.User.from_google_userinfo(userinfo)
                token = jwt.encode({
                    'id': user.id,
                    'exp': self.get_exp()
                }, user.secret)
                msg.code_debug(_path, 'Rendering login.html ...')
                self.render('login.html', token=token)

        except oa2_client.FlowExchangeError:
            self.render('boxes.html',
                        classes={'system'},
                        critical='Error de autenticación!')

    def get_scheme(self):
        if 'Scheme' in self.request.headers:
            return self.request.headers['Scheme']
        else:
            return self.request.protocol

    @property
    def state(self):
        if hasattr(self, '_state'):
            return self._state

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

        if state:
            self._state = jwt.decode(state, secrets['simple'])
        else:
            self._state = None

        return self._state

    def get_exp(self):
        if self.state:
            delta = conf.long_account_exp \
                if self.state['remember'] else \
                conf.short_account_exp
            return datetime.utcnow() + timedelta(**delta)
        else:
            return conf.short_account_exp

    @coroutine
    def request_disc_doc(self):
        _path = msg.join_path(self._path, 'request_disc_doc')

        def _req_disc_doc():
            dd = self.disc_doc_client.request(
                'https://accounts.google.com/.well-known/'
                'openid-configuration', 'GET')
            return self.decode_httplib2_json(dd)

        if self.disc_doc is None:
            msg.code_debug(_path, 'Requesting discovery document ...')

            with ThreadPoolExecutor(1) as thread:
                self.__class__.disc_doc = thread.submit(_req_disc_doc)
                self.disc_doc = \
                    yield self.__class__.disc_doc
                # Este yield tiene que ir dentro, ya que el
                # thread no se comenzará a ejecutar si no se
                # yieldea y no se puede comenzar a ejecutar
                # fuera del with ya que ahí no existe ... o
                # algo asi XD :C
                self.__class__.disc_doc = None
                msg.code_debug(_path, 'self.__class__.disc_doc = None')

            msg.code_debug(_path, 'Discovery document arrived!')

        else:
            msg.code_debug(_path, 'Waiting for discovery document ...')

            self.disc_doc = yield self.disc_doc
            msg.code_debug(_path, 'Got the discovery document!')

    def decode_httplib2_json(self, response):
        return json.loads(response[1].decode('utf-8'))
Example #27
0
from functools import partialmethod

import jwt
from tornado.gen import coroutine
from pymongo.errors import OperationFailure

import src
from controller import MSGHandler
from backend_modules import router
from src import messages as msg
from src.db import User, Code, NoObjectReturnedFromDB, \
    ConditionNotMetError, Course, Room
from src.pub_sub import MalformedMessageError
from src.wsclass import subscribe

_path = msg.join_path('panels', 'user')

MSGHandler.send_user_not_loaded_error = partialmethod(
    MSGHandler.send_error,
    'userNotLoaded',
    description='There was no loaded user when '
    'this message arrived.')

MSGHandler.send_room_not_loaded_error = partialmethod(
    MSGHandler.send_error,
    'roomNotLoaded',
    description='There was no loaded room when '
    'this message arrived.')


def _logout_and_close(self, reason):
Example #28
0
# -*- coding: UTF-8 -*-

from warnings import warn

from tornado.ioloop import IOLoop

from src import messages as msg
from src.exceptions import NotDictError

_path = msg.join_path('src', 'pub_sub')


class PubSub(object):

    """Implement the PubSub pattern.

    This class is designed to implement the PubSub pattern,
    in a way that is compatible with Tornado's coroutines.

    .. automethod:: __init__
    """

    _path = msg.join_path(_path, 'PubSub')

    def __init__(self, name='pub_sub_instance',
                 send_function=None):
        """Initialize the new PubSub object.

        :param str name:
            Name used to identify this object in debug
            messages.
Example #29
0
class UserWSC(src.wsclass.WSClass):
    """Process user messages"""

    _path = msg.join_path(_path, 'UserWSC')

    @staticmethod
    def should_run_room_deassign_course(has_course, was_none, was_student,
                                        was_teacher, is_none, is_student,
                                        is_teacher, distinct_room):

        return was_teacher and has_course and (is_student
                                               or is_teacher and distinct_room)

    @staticmethod
    def should_run_user_deassign_course(has_course, was_none, was_student,
                                        was_teacher, is_none, is_student,
                                        is_teacher, distinct_room):

        student_condition = \
            was_student and (
                not is_student or distinct_room)

        teacher_condition = \
            was_teacher and not is_none and (
                not is_teacher or distinct_room)

        return has_course and (student_condition or teacher_condition)

    @staticmethod
    def should_run_use_seat(has_course, was_none, was_student, was_teacher,
                            is_none, is_student, is_teacher, distinct_room):

        return is_student

    @staticmethod
    def should_run_logout_other_instances(has_course, was_none, was_student,
                                          was_teacher, is_none, is_student,
                                          is_teacher, distinct_room):

        return \
            was_student or \
            is_student or \
            was_none and not is_none or \
            is_teacher and distinct_room

    @staticmethod
    def should_run_load_course(has_course, was_none, was_student, was_teacher,
                               is_none, is_student, is_teacher, distinct_room):

        return \
            has_course and not distinct_room and (
                was_student and is_student or
                was_teacher and is_teacher
            )

    @staticmethod
    def should_run_redirect_to_teacher_view(has_course, was_none, was_student,
                                            was_teacher, is_none, is_student,
                                            is_teacher, distinct_room):

        return was_teacher and is_none

    def __init__(self, handler):
        super().__init__(handler)
        self.session_start_ok = False
        self.block_logout = False
        self.user_state_at_exclusive_login = None
        self.dont_leave_seat = False

    @coroutine
    def load_user(self, token):
        try:
            uid = jwt.decode(token, verify=False)['id']
            user = yield User.get(uid)
            jwt.decode(token, user.secret)
            self.handler.user = user

        except NoObjectReturnedFromDB as norfdb:
            ite = jwt.InvalidTokenError(
                'No user was found in the database for '
                'this token.')
            raise ite from norfdb

    @subscribe('teacherMessage', 'l')
    @coroutine
    def redirect_message_if_user_is_teacher(self, message, content=True):
        """Redirects a message if the user is a teacher.

        This coroutine redirects a message to the local
        channel only if the current user is a teacher.

        :param dict message:
            The message that should be redirected if the
            user is a teacher.

        :param bool content:
            If ``True``, just the object corresponding to
            the ``'content'`` key of ``message`` will be
            sent.
            If ``False``, the whole message will be sent.

        :raises MalformedMessageError:
            If ``content`` is ``True``, but ``message``
            doesn't have the ``'content'`` key.

        :raises NotDictError:
            If ``message`` is not a dictionary.

        :raises NoMessageTypeError:
            If the message or it's content doesn't have the
            ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If ``send_function`` of the ``PubSub`` object
            wasn't specified during object creation and
            there's no registered action for this message
            type.

        :raises AttributeError:
            If the user is not yet loaded or if the user is
            ``None``.
        """
        try:
            if self.handler.user.status == 'room':
                self.redirect_to('l', message, content)
        except:
            raise

    @subscribe('logout', 'l')
    def logout(self, message):
        try:
            if self.block_logout:
                self.block_logout = False
            else:
                self.user_state_at_exclusive_login = \
                    message.get(
                        'user_state_at_exclusive_login')

                self.dont_leave_seat = message.get('dont_leave_seat', False)

                self.handler.logout_and_close(message['reason'])

        except KeyError as ke:
            if 'reason' not in message:
                mme = MalformedMessageError(
                    "'reason' key not found in message.")
                raise mme from ke
            else:
                raise

    @subscribe('userMessage', channels={'w', 'l'})
    def send_user_message(self, message):
        """Send a message to all instances of a single user.

        The user id is appended to the message type and
        ``message`` is sent through the database.

        .. todo::
            *   Change the message type of the subscription
                to be organised by namespace.
            *   Change this method so that it uses
                self.handler.user_msg_type.
        """
        try:
            message['type'] = '{}({})'.format(message['type'],
                                              self.handler.user.id)

            self.redirect_to('d', message)

        except MalformedMessageError:
            self.handler.send_malformed_message_error(message)
            msg.malformed_message(_path, message)

        except AttributeError:
            if not hasattr(self.handler, 'user'):
                self.handler.send_user_not_loaded_error(message)
            else:
                raise

    @subscribe('user.message.frontend.send', channels={'w', 'l'})
    def send_frontend_user_message(self, message):
        """Send a message to all clients of a single user.

        .. todo::
            *   Review the error handling and documentation
                of this funcion.
        """
        try:
            self.pub_subs['d'].send_message({
                'type': self.handler.user_msg_type,
                'content': {
                    'type': 'toFrontend',
                    'content': message['content']
                }
            })

        except:
            raise

    def sub_to_user_messages(self):
        """Route messages of the same user to the local PS.

        This method subscribes
        :meth:`backend_modules.router.RouterWSC.to_local` to
        the messages of type ``userMessage(uid)`` coming
        from the database pub-sub. Where ``uid`` is the
        current user id.

        After the execution of this method,
        ``self.handler.user_msg_type`` contains the message
        type to be used to send messages to all instances of
        the user.
        """
        self.handler.user_msg_type = \
            'userMessage({})'.format(self.handler.user.id)

        router_object = self.handler.ws_objects[router.RouterWSC]
        self.register_action_in(self.handler.user_msg_type,
                                action=router_object.to_local,
                                channels={'d'})

    def send_session_start_error(self, message, causes):
        """Send a session error message to the client.

        :param dict message:
            The message that caused the error.

        :param str causes:
            A string explaining the posible causes of the
            error.
        """
        try:
            self.handler.send_error(
                'session.start.error', message,
                'La sesión no se ha podido iniciar. ' + causes)

        except TypeError as e:
            if not isinstance(message, dict):
                te = TypeError('message should be a dictionary.')
                raise te from e

            elif not isinstance(causes, str):
                te = TypeError('causes should be a string.')
                raise te from e

            else:
                raise

    @coroutine
    def load_room_code(self, room_code_str):
        """Load the room code and room from the db.

        ..todo::
            * Write error handling.
        """
        if room_code_str == 'none':
            room_code = None
            room = None
            code_type = 'none'
            room_name = None
            seat_id = None
        else:
            room_code = yield Code.get(room_code_str)
            room = yield room_code.room
            code_type = room_code.code_type.value
            room_name = room.name
            seat_id = room_code.seat_id

        self.handler.room_code = room_code
        self.handler.room = room

        return (code_type, room_name, seat_id)

    def redirect_to_teacher_view(self, room_code, message):
        """Redirect the client to the current teacher view.

        .. todo:
            *   Use send_session_start_error instead of
                handler.send_error.
        """
        if room_code == 'none':
            err_msg = "Can't redirect user to the " \
                "teacher view. This can be caused by an " \
                "inconsistency in the database."

            self.handler.send_error('databaseInconsistency', message, err_msg)

            raise Exception(err_msg)

        self.pub_subs['w'].send_message({
            'type': 'replaceLocation',
            'url': room_code
        })

    @subscribe('session.start', 'w')
    @coroutine
    def startSession(self, message):
        """Start a user session

        .. todo::
            *   Add a variable that indicates the last stage
                that was executed successfully. So that the
                finally clause can clean the mess properly.
        """
        try:
            yield self.load_user(message['token'])
            self.sub_to_user_messages()
            code_type, room_name, seat_id = \
                yield self.load_room_code(
                    message['room_code'])

            user = self.handler.user
            room_code = self.handler.room_code
            room = self.handler.room

            course_id = user.course_id
            has_course = course_id is not None

            was_none = (user.status == 'none')
            was_student = (user.status == 'seat')
            was_teacher = (user.status == 'room')

            is_none = (code_type == 'none')
            is_student = (code_type == 'seat')
            is_teacher = (code_type == 'room')

            distinct_room = not (room_name is None or user.room_name is None
                                 or room_name == user.room_name)

            same_seat = (seat_id == user.seat_id) and \
                not distinct_room

            transition_data = (has_course, was_none, was_student, was_teacher,
                               is_none, is_student, is_teacher, distinct_room)

            # Redirect to Teacher View
            if self.should_run_redirect_to_teacher_view(*transition_data):
                self.redirect_to_teacher_view(user.room_code, message)
                # The rest of the code is not executed.
                return

            # Room Deassign Course
            '''This should always run before "User Deassign
            Course"'''
            if self.should_run_room_deassign_course(*transition_data):
                if distinct_room:
                    r = yield Room.get(user.room_name)
                else:
                    r = room

                yield r.deassign_course(course_id)

            # User Deassign Course
            if self.should_run_user_deassign_course(*transition_data):
                yield user.deassign_course()

            # Logout Other Instances
            if self.should_run_logout_other_instances(*transition_data):
                self.block_logout = True
                self.pub_subs['d'].send_message({
                    'type': self.handler.user_msg_type,
                    'content': {
                        'type': 'logout',
                        'reason': 'exclusiveLogin',
                        'user_state_at_exclusive_login': user._data,
                        'dont_leave_seat': same_seat,
                    }
                })

            # Use Seat
            if self.should_run_use_seat(*transition_data) \
                    and (not same_seat or
                         user.instances == 0):
                yield room.use_seat(room_code.seat_id)

            # Load Course
            if self.should_run_load_course(*transition_data):
                self.handler.course = yield Course.get(course_id)

            # Increase Instances
            yield self.handler.user.increase_instances()

            yield self.handler.user.store_dict({
                'status':
                code_type,
                'room_code':
                message['room_code'],
                'room_name':
                room_name,
                'seat_id':
                seat_id,
            })

        except jwt.InvalidTokenError:
            self.handler.logout_and_close('invalidToken')

        except ConditionNotMetError:
            self.send_session_start_error(
                message, 'Es probable que este error se daba a que '
                'el asiento que desea registrar ya está '
                'usado.')

        except OperationFailure:
            self.send_session_start_error(
                message, 'Una operación de la base de datos ha '
                'fallado.')

        except KeyError:
            keys_in_message = all(
                map(lambda k: k in message, ('token', 'room_code')))
            if not keys_in_message:
                self.handler.send_malformed_message_error(message)
            else:
                raise

        else:
            self.session_start_ok = True

            self.pub_subs['w'].send_message({
                'type': 'session.start.ok',
                'code_type': code_type,
                'course_id': user.course_id
            })

        finally:
            if not self.session_start_ok:
                pass

    @subscribe('getUserName', 'w')
    def get_user_name(self, message):
        """Send the user's name to the client.

        .. todo::
            *   Re-raise attribute error
            *   review error handling.
        """
        try:
            name = self.handler.user.name
            self.pub_subs['w'].send_message({'type': 'userName', 'name': name})
        except AttributeError:
            self.handler.send_user_not_loaded_error(message)

    @coroutine
    def end_room_usage(self, user, is_teacher, is_student):
        try:
            room_code = self.handler.room_code
            room = yield room_code.room

            # Room Deassign Course
            if is_teacher and \
                    user.course_id is not None and \
                    user.instances == 1:
                yield room.deassign_course(user.course_id)

            # Leave seat
            if is_student and not self.dont_leave_seat:
                yield room.leave_seat(room_code.seat_id)

        except AttributeError:
            if not hasattr(self.handler, 'room_code') or \
                    self.handler.room_code is None:
                msg.code_warning(
                    msg.join_path(__name__, self.end.__qualname__),
                    "room_code wasn't initialized at "
                    "{.__class__.__name__}'s "
                    "end.".format(self))

            else:
                raise

    @coroutine
    def end(self):
        try:
            yield super().end()

            if not self.session_start_ok:
                return

            if self.user_state_at_exclusive_login:
                user = User(self.user_state_at_exclusive_login)
            else:
                user = self.handler.user
                yield user.sync('instances')

            is_teacher = (user.status == 'room')
            is_student = (user.status == 'seat')

            # Room Deassign Course
            # Leave seat
            yield self.end_room_usage(user, is_teacher, is_student)

            # User Deassign Course
            yield user.deassign_course(if_last_instance=is_teacher)

            # Decrease Instances
            # (can modify status and course_id)
            yield user.decrease_instances()

        except:
            raise
Example #30
0
from functools import partialmethod

import jwt
from tornado.gen import coroutine
from pymongo.errors import OperationFailure

import src
from controller import MSGHandler
from backend_modules import router
from src import messages as msg
from src.db import User, Code, NoObjectReturnedFromDB, \
    ConditionNotMetError, Course, Room
from src.pub_sub import MalformedMessageError
from src.wsclass import subscribe

_path = msg.join_path('panels', 'user')

MSGHandler.send_user_not_loaded_error = partialmethod(
    MSGHandler.send_error,
    'userNotLoaded',
    description='There was no loaded user when '
                'this message arrived.'
)

MSGHandler.send_room_not_loaded_error = partialmethod(
    MSGHandler.send_error,
    'roomNotLoaded',
    description='There was no loaded room when '
    'this message arrived.'
)
Example #31
0
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from warnings import warn

from tornado.ioloop import IOLoop

from src import messages as msg
from src.exceptions import NotDictError

_path = msg.join_path('src', 'pub_sub')


class PubSub(object):
    """Implement the PubSub pattern.

    This class is designed to implement the PubSub pattern,
    in a way that is compatible with Tornado's coroutines.

    .. automethod:: __init__
    """

    _path = msg.join_path(_path, 'PubSub')

    def __init__(self, name='pub_sub_instance', send_function=None):
        """Initialize the new PubSub object.
Example #32
0
class MSGHandler(WebSocketHandler):
    """Serve the WebSocket clients.

    An instance of this class is created every time a
    client connects using WebSocket. The instances of this
    class deliver messages to a group of objects
    specialized in attending a group of messages.
    """

    _path = msg.join_path(_path, 'MSGHandler')

    ws_classes = []
    clients = set()
    client_count = 0  # Total clients that have connected

    @classmethod
    @coroutine
    def stop(cls):
        for client in cls.clients.copy():
            yield client.end()

    def initialize(self):
        _path = msg.join_path(self._path, 'initialize')
        msg.code_debug(
            _path, 'New connection established! {0} '
            '({0.request.remote_ip})'.format(self))

        self.local_pub_sub = OwnerPubSub(name='local_pub_sub')

        self.ws_pub_sub = OwnerPubSub(name='ws_pub_sub',
                                      send_function=self.write_message)
        self.ws_objects = {
            ws_class: ws_class(self)
            for ws_class in self.ws_classes
        }

        self.__class__.clients.add(self)
        self.__class__.client_count += 1

        self.clean_closed = False
        self.ping_timeout_handle = None

    def open(self):
        IOLoop.current().spawn_callback(self.on_pong, b'1')

    @classmethod
    def add_class(cls, wsclass):
        cls.ws_classes.append(wsclass)

    @classmethod
    def broadcast(cls, message):
        for client in cls.clients:
            client.ws_pub_sub.send_message(message)

    def on_message(self, message):
        """Process messages when they arrive.

        :param str message:
            The received message. This should be a valid
            json document.
        """
        try:
            # Throws ValueError
            message = json.loads(message)

            self.ws_pub_sub.execute_actions(message)

        except NoActionForMsgTypeError:
            self.send_error(
                'noActionForMsgType', message,
                "The client has sent a message for which "
                "there is no associated action.")
            msg.no_action_for_msg_type(_path, message)

        except (NotDictError, NoMessageTypeError, ValueError):
            self.send_malformed_message_error(message)
            msg.malformed_message(_path, message)

    @coroutine
    def on_pong(self, data):
        """Clear the timeout, sleep, and send a new ping.

        .. todo::
            *   Document the times used in this method.
                The calculations are in my black notebook
                XD.
        """
        try:
            if self.ping_timeout_handle is not None:
                IOLoop.current().remove_timeout(self.ping_timeout_handle)

            yield sleep(conf.ping_sleep)

            self.ping(b'1')
            self.ping_timeout_handle = \
                IOLoop.current().call_later(
                    conf.ping_timeout, self.close)

        except WebSocketClosedError:
            pass

        except:
            raise

    def send_error(self, critical_type, message, description):
        self.ws_pub_sub.send_message({
            'type': 'critical',
            'critical_type': critical_type,
            'message': message,
            'description': description
        })

    send_malformed_message_error = partialmethod(
        send_error,
        'malformedMessage',
        description="The client has sent a message which "
        "either isn't in JSON format, is not a "
        "single JSON object, does not have a "
        "'type' field or at least one "
        "attribute is not consistent with the "
        "others.")

    def write_message(self, message, binary=False):
        try:
            super().write_message(message, binary)

        except WebSocketClosedError:
            if not self.clean_closed:
                raise

    @coroutine
    def end(self):
        """Clean up the associated objects

        This coroutine calls :meth:`src.wsclass.WSClass.end`
        for all objects in ``self.ws_objects`` and it
        removes ``self`` from ``self.__class__.clients``.

        This coroutine is setup to be called when
        the WebSocket connection closes or when the program
        ends.
        """
        try:
            exceptions = []
            for ws_object in self.ws_objects.values():
                try:
                    yield ws_object.end()
                except:
                    exceptions.append(exc_info())

            for exception in exceptions:
                print_exception(*exception)

            self.__class__.clients.discard(self)

            msg.code_debug(
                msg.join_path(__name__, self.end.__qualname__),
                'Connection closed! {0} '
                '({0.request.remote_ip})'.format(self))

        except:
            raise

    @coroutine
    def on_close(self):
        try:
            yield self.end()

        except:
            raise
Example #33
0
    def get(self):
        _path = msg.join_path(self._path, 'get')
        try:
            redirect_uri = urlunparse(
                (self.get_scheme(), self.request.host,
                 conf.login_path, '', '', '')
            )
            # remember the user for a longer period of time
            remember = self.get_argument('remember', False)
            room_code = self.get_argument('room_code',
                                          False)
            state = jwt.encode({'remember': remember,
                                'room_code': room_code},
                               secrets['simple'])
            flow = oa2_client.OAuth2WebServerFlow(
                google_secrets['web']['client_id'],
                google_secrets['web']['client_secret'],
                scope='openid profile',
                redirect_uri=redirect_uri,
                state=state)

            auth_code = self.get_argument('code', False)

            if not auth_code:
                auth_uri = flow.step1_get_authorize_url()
                self.redirect(auth_uri)

            else:
                with ThreadPoolExecutor(1) as thread:
                    credentials = yield thread.submit(
                        flow.step2_exchange, auth_code)
                # Intercambiar el codigo antes que nada para
                # evitar ataques

                yield self.request_disc_doc()

                userinfo_endpoint = \
                    self.disc_doc['userinfo_endpoint']

                http_auth = credentials.authorize(
                    httplib2.Http())

                with ThreadPoolExecutor(1) as thread:
                    userinfo = yield thread.submit(
                        http_auth.request,
                        userinfo_endpoint)
                userinfo = self.decode_httplib2_json(
                    userinfo)
                # https://developers.google.com/+/api/
                # openidconnect/getOpenIdConnect

                user = yield db.User.from_google_userinfo(
                    userinfo)
                token = jwt.encode({'id': user.id,
                                    'exp': self.get_exp()},
                                   user.secret)
                msg.code_debug(_path,
                               'Rendering login.html ...')
                self.render('login.html', token=token)

        except oa2_client.FlowExchangeError:
            self.render('boxes.html',
                        classes={'system-panel'},
                        critical='Error de autenticación!')
Example #34
0
class PubSub(object):
    """Implement the PubSub pattern.

    This class is designed to implement the PubSub pattern,
    in a way that is compatible with Tornado's coroutines.

    .. automethod:: __init__
    """

    _path = msg.join_path(_path, 'PubSub')

    def __init__(self, name='pub_sub_instance', send_function=None):
        """Initialize the new PubSub object.

        :param str name:
            Name used to identify this object in debug
            messages.

        :param callable send_function:
            Function or coroutine to be called when sending
            a message. This function should not be called
            directly. When sending a message, call
            ``send_message`` instead of ``send_function``.
        """
        self.name = name
        self.send_function = send_function
        self.actions = {}

    def __str__(self):
        return self.name

    def __repr__(self):
        """Return a string representation of the object."""
        t = '{0.__class__.__name__}' \
            "('{0.name}', {0.send_function.__qualname__})"
        return t.format(self)

    def register(self, msg_type, action):
        if msg_type in self.actions:
            self.actions[msg_type].add(action)
        else:
            self.actions[msg_type] = {action}

    def send_message(self, message):
        """Send a message

        ``self.send_function`` is used if it was specified
        during object creation. If not,
        ``self.execute_actions`` is used.

        The message will be sent on the next iteration of
        the IOLoop. If you need to send the message
        immediately use "self.send_function" directly.

        :param dict message: The message to be sent.

        :raises NotDictError:
            If ``message`` is not a dictionary.

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If ``self.send_function`` wasn't specified
            during object creation and there's no
            registered action for this message type.
        """
        _path = msg.join_path(self._path, 'send_message')

        if not isinstance(message, dict):
            raise NotDictError('message', message)

        if 'type' not in message:
            raise NoMessageTypeError(message)

        msg.code_debug(_path, 'Sending message: {}.'.format(message))

        if self.send_function is None:
            self.execute_actions(message)
        else:
            IOLoop.current().spawn_callback(self.send_function, message)

    def execute_actions(self, message):
        """Execute actions associated to the type of message

        :param dict message: The message to be sent

        :raises NoMessageTypeError:
            If ``message`` doesn't have the ``'type'`` key.

        :raises NoActionForMsgTypeError:
            If there's no registered action for this message
            type.

        :raises NotDictError:
            If ``message`` is not an instance of ``dict``.
        """
        _path = msg.join_path(self._path, 'execute_actions')
        msg.code_debug(_path, 'Message arrived: {}.'.format(message))

        try:
            for action in self.actions[message['type']]:
                IOLoop.current().spawn_callback(action, message)
        except TypeError as te:
            if not isinstance(message, dict):
                nde = NotDictError('message', message)
                raise nde from te
            else:
                raise

        except KeyError as ke:
            if 'type' not in message:
                raise NoMessageTypeError(message) from ke

            elif message['type'] not in self.actions:
                nafmte = NoActionForMsgTypeError(message, self.name)
                raise nafmte from ke
            else:
                raise

    def remove(self, msg_type, action):
        """Remove the action asociated with ``msg_type``.

        :param str msg_type:
            The message type that is asociated with
            ``action``.

        :param callable action:
            A function that was previously registered to
            ``msg_type``.

        :raises NoActionForMsgTypeError:
            If there's no registered action for this message
            type.
        """
        try:
            self.actions[msg_type].discard(action)

            if not self.actions[msg_type]:
                del self.actions[msg_type]

        except KeyError as ke:
            if msg_type not in self.actions:
                nafmte = NoActionForMsgTypeError(msg_type, self.name)
                raise nafmte from ke
            else:
                raise