示例#1
0
 def __init__(self):
     self.config = options
     self.dbsession = dbsession
     self.cache = MemcachedConnect()
     self.epoch = None  # Date/time of first snapshot
     self._load()
     self.event_manager = EventManager.instance()
示例#2
0
class BaseHandler(RequestHandler):
    """ User handlers extend this class """

    csp = {
        "default-src": set(["'self'"]),
        "script-src": set(["'self'"]),
        "connect-src": set(["'self'"]),
        "frame-src": set(["'self'"]),
        "img-src": set(["'self'"]),
        "media-src": set(["'none'"]),
        "font-src": set(["'self'"]),
        "object-src": set(["'none'"]),
        "style-src": set(["'self'"]),
    }
    _session = None
    dbsession = dbsession
    chatsession = chatsession
    _memcached = None
    new_events = []
    io_loop = IOLoop.instance()
    event_manager = EventManager.instance()
    config = options  # backward compatability

    def initialize(self):
        """ Setup sessions, etc """
        self.add_content_policy("connect-src", self.config.origin)
        # We need this for a few things, and so far as I know it doesn't
        # present too much of a security risk - TODO: no longer require
        # inline styles
        self.add_content_policy("style-src", "'unsafe-inline'")

    def get_current_user(self):
        """ Get current user object from database """
        if self.session is not None:
            try:
                return User.by_uuid(self.session["user_uuid"])
            except KeyError:
                logging.exception("Malformed session: %r" % self.session)
            except:
                logging.exception("Failed call to get_current_user()")
        return None

    def start_session(self):
        """ Starts a new session """
        self.session = self._create_session()
        flags = {
            "expires": self.session.expires,
            "path": "/",
            "HttpOnly": True
        }
        if self.config.ssl:
            flags["Secure"] = True
        self.set_secure_cookie("session_id", self.session.session_id, **flags)

    def add_content_policy(self, src, policy):
        """ Add to the existing CSP header """
        if not src.endswith("-src"):
            src += "-src"
        if src in self.csp:
            self.csp[src].add(policy)
            self._refresh_csp()
        else:
            raise ValueError("Invalid content source")

    def clear_content_policy(self, src):
        """ Clear a content source in the existing CSP header """
        if not src.endswith("-src"):
            src += "-src"
        if src in self.csp:
            self.csp[src] = set()
            self._refresh_csp()
        else:
            raise ValueError("Invalid content source")

    def _refresh_csp(self):
        """ Rebuild the Content-Security-Policy header """
        _csp = []
        for src, policies in list(self.csp.items()):
            if len(policies):
                _csp.append("%s %s; " % (src, " ".join(policies)))
        csp = "".join(_csp)
        # Disabled until i can figure out the bug
        # self.set_header("Content-Security-Policy", csp)

    @property
    def memcached(self):
        """ Connects to Memcached instance """
        if self._memcached is None:
            self._memcached = MemcachedConnect()
        return self._memcached

    def _create_session(self):
        """ Creates a new session """
        kwargs = {
            "connection": self.memcached,
            "ip_address": self.request.remote_ip
        }
        new_session = MemcachedSession(**kwargs)
        new_session.save()
        return new_session

    def flush_memcached(self):
        if self._memcached is not None:
            self._memcached.flush_all()

    @property
    def session(self):
        if self._session is None:
            session_id = self.get_secure_cookie("session_id")
            if session_id is not None:
                self._session = self._get_session(session_id)
        return self._session

    @session.setter
    def session(self, new_session):
        self._session = new_session

    def _get_session(self, session_id):
        kwargs = {
            "connection": self.memcached,
            "session_id": session_id,
            "ip_address": self.request.remote_ip,
        }
        old_session = MemcachedSession.load(**kwargs)
        if old_session and not old_session.is_expired():
            old_session.refresh()
            return old_session
        else:
            return None

    def set_default_headers(self):
        """
        Set security HTTP headers, and add some troll-y version headers
        """
        self.set_header("Server", "Microsoft-IIS/7.5")
        self.add_header("X-Powered-By", "ASP.NET")
        self.add_header("X-Frame-Options", "DENY")
        self.add_header("X-XSS-Protection", "1; mode=block")
        self.add_header("X-Content-Type-Options", "nosniff")
        self._refresh_csp()
        if self.config.ssl:
            self.add_header("Strict-Transport-Security",
                            "max-age=31536000; includeSubDomains;")

    def write_error(self, status_code, **kwargs):
        """ Write our custom error pages """
        trace = "".join(traceback.format_exception(*kwargs["exc_info"]))
        logging.error("Request from %s resulted in an error code %d:\n%s" %
                      (self.request.remote_ip, status_code, trace))
        if status_code in [403]:
            # This should only get called when the _xsrf check fails,
            # all other '403' cases we just send a redirect to /403
            # self.render('public/403.html', locked=False, xsrf=True)
            self.redirect("/logout")  # just log them out
        else:
            if not self.config.debug:
                # Never tell the user we got a 500
                self.render("public/404.html")
            else:
                # If debug mode is enabled, just call Tornado's write_error()
                super(BaseHandler, self).write_error(status_code, **kwargs)

    def get(self, *args, **kwargs):
        """ Placeholder, incase child class does not impl this method """
        self.render("public/404.html")

    def post(self, *args, **kwargs):
        """ Placeholder, incase child class does not impl this method """
        self.render("public/404.html")

    def put(self, *args, **kwargs):
        """ Log odd behavior, this should never get legitimately called """
        logging.warn("%s attempted to use PUT method" % self.request.remote_ip)

    def delete(self, *args, **kwargs):
        """ Log odd behavior, this should never get legitimately called """
        logging.warn("%s attempted to use DELETE method" %
                     self.request.remote_ip)

    def head(self, *args, **kwargs):
        """ Ignore it """
        logging.warn("%s attempted to use HEAD method" %
                     self.request.remote_ip)

    def options(self, *args, **kwargs):
        """ Log odd behavior, this should never get legitimately called """
        logging.warn("%s attempted to use OPTIONS method" %
                     self.request.remote_ip)

    def on_finish(self, *args, **kwargs):
        """ Called after a response is sent to the client """
        self.dbsession.close()

    def timer(self):
        timer = None
        if self.application.settings["freeze_scoreboard"]:
            timerdiff = self.application.settings[
                "freeze_scoreboard"] - time.time()
            if timerdiff <= 0:
                timerdiff = 0
                if self.application.settings["stop_timer"]:
                    self.application.settings["stop_timer"] = False
                    self.stop_game()
            timer = str(timerdiff)
        return timer

    def start_game(self):
        """ Start the game and any related callbacks """
        if not self.application.settings["game_started"]:
            logging.info("The game is about to begin, good hunting!")
            self.application.settings["game_started"] = True
            self.application.settings["history_callback"].start()
            if self.config.use_bots:
                self.application.settings["score_bots_callback"].start()
            # Fire game start webhook
            send_game_start_webhook()

    def stop_game(self):
        """ Stop the game and all callbacks """
        if self.application.settings["game_started"]:
            logging.info("The game is stopping ...")
            self.application.settings["game_started"] = False
            if self.application.settings["history_callback"]._running:
                self.application.settings["history_callback"].stop()
            if self.application.settings["score_bots_callback"]._running:
                self.application.settings["score_bots_callback"].stop()
            # Fire game stop webhook
            send_game_stop_webhook()

    def get_user_locale(self):
        """
        Get user lang value from config.
        If None is returned, Tornado fall back to get_browser_locale()
        """
        if len(self.config.force_locale) > 0:
            return locale.get(self.config.force_locale)
        else:
            """
            This is a work around as Tornado get_browser_locale() is not returning the closest match.
            https://github.com/tornadoweb/tornado/issues/1858
            https://github.com/moloch--/RootTheBox/issues/367
            """
            codes = self.request.headers.get("Accept-Language")
            if codes:
                for code in codes.split(","):
                    code = code.split(";")[0]
                    for l in locale.get_supported_locales():
                        if code.lower() == l.split("_")[0]:
                            return locale.get(l)
        return None
示例#3
0
 def memcached(self):
     """ Connects to Memcached instance """
     if self._memcached is None:
         self._memcached = MemcachedConnect()
     return self._memcached
示例#4
0
class GameHistory(object):
    """
    List-like object to store game history, with cache to avoid
    multiple large database reads.
    """

    def __init__(self):
        self.config = options
        self.dbsession = dbsession
        self.cache = MemcachedConnect()
        self.epoch = None  # Date/time of first snapshot
        self.event_manager = EventManager.instance()

    def _load(self):
        """ Moves snapshots from db into the cache """
        logging.info("Loading game history from database ...")
        snaps = Snapshot.all()
        if len(snaps) > 0:
            snap = snaps[0]
        else:
            snap = self.__now__()  # Take starting snapshot
        self.epoch = snap.created
        try:
            max_index = len(self)
            start_index = snap.id if len(self) <= (snap.id + 9) else max_index - 9
            for index in range(start_index, max_index + 1):
                snapshot = Snapshot.by_id(index)
                if snapshot and self.cache.get(snapshot.key) is None:
                    logging.info(
                        "Cached snapshot (%d of %d)" % (snapshot.id, max_index)
                    )
                    self.cache.set(snapshot.key, snapshot.to_dict())
            logging.info("History load complete.")
        except TypeError:
            logging.error("Error Loading Cache (try to restart memcached)")
        except KeyboardInterrupt:
            logging.info("History load stopped by user.")

    def take_snapshot(self, *args):
        """ Take a snapshot of the current game data """
        snapshot = self.__now__()
        if snapshot is not None:
            self.cache.set(snapshot.key, snapshot.to_dict())
            self.event_manager.push_history(snapshot.to_dict())

    def get_flag_history_by_name(self, name, start, stop=None):
        """ Retrieves flag capture history for a team """
        snapshots = self[start:] if stop is None else self[start:stop]
        series = []
        for snapshot in snapshots:
            if name in snapshot["scoreboard"]:
                flags = snapshot["scoreboard"][name]["flags"]
                series.append((snapshot["timestamp"], len(flags)))
        return series

    def get_money_history_by_name(self, name, start, stop=None):
        """ Retrieves money history for a team """
        snapshots = self[start:] if stop is None else self[start:stop]
        series = []
        for snapshot in snapshots:
            if name in snapshot["scoreboard"]:
                money = snapshot["scoreboard"][name]["money"]
                series.append((snapshot["timestamp"], money))
        return series

    def get_bot_history_by_name(self, name, start, stop=None):
        """ Retrieves money history for a team """
        snapshots = self[start:] if stop is None else self[start:stop]
        series = []
        for snapshot in snapshots:
            if name in snapshot["scoreboard"]:
                bots = snapshot["scoreboard"][name]["bots"]
                series.append((snapshot["timestamp"], bots))
        return series

    def __now__(self):
        """ Returns snapshot object it as a dict """
        snapshot = Snapshot()
        bot_manager = BotManager.instance()
        # self.dbsession = DBSession()
        for team in Team.all():
            if not team.locked:
                snapshot_team = SnapshotTeam(
                    team_id=team.id,
                    money=team.money,
                    bots=bot_manager.count_by_team(team.name),
                )
                snapshot_team.game_levels = team.game_levels
                snapshot_team.flags = team.flags
                self.dbsession.add(snapshot_team)
                self.dbsession.flush()
                snapshot.teams.append(snapshot_team)
        self.dbsession.add(snapshot)
        self.dbsession.commit()
        return snapshot

    def __iter__(self):
        for snapshot in self:
            yield snapshot.to_dict()

    def __contains__(self, index):
        return True if Snapshot.by_id(index) is not None else False

    def __len__(self):
        """ Return length of the game history """
        return self.dbsession.query(Snapshot).order_by(desc(Snapshot.id)).first().id

    def __getitem__(self, key):
        """ Implements slices and indexs """
        if isinstance(key, slice):
            ls = [self[index] for index in range(*key.indices(len(self)))]
            return [item for item in ls if item is not None]
        elif isinstance(key, int):
            if key < 0:  # Handle negative indices
                key += len(self)
            if key >= len(self):
                raise IndexError("The index (%d) is out of range." % key)
            return self.__at__(key)
        else:
            raise TypeError("Invalid index argument to GameHistory")

    def __at__(self, index):
        """ Get snapshot at specific index """
        key = Snapshot.to_key(index + 1)
        if self.cache.get(key) is not None:
            return self.cache.get(key)
        else:
            snapshot = Snapshot.by_id(index + 1)
            if snapshot is not None:
                self.cache.set(snapshot.key, snapshot.to_dict())
                return snapshot.to_dict()
        return None