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()
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
def memcached(self): """ Connects to Memcached instance """ if self._memcached is None: self._memcached = MemcachedConnect() return self._memcached
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