def board_phase(): LOG.info("Phase request") phase = _phase_from_trains(request.args.get("trains")) LOG.info(f"Phase: {phase}") return jsonify({"phase": phase})
def trains(): LOG.info("Train request.") all_trains = get_train_info(g.game_name) train_strs = [ str(train) for train in sorted(all_trains, key=lambda train: (train.collect, train.visit)) ] LOG.info(f"Train response: {all_trains}") return jsonify({"trains": train_strs})
def legal_tiles(): game = get_game(g.game_name) coord = request.args.get("coord") LOG.info(f"Legal tiles request for {coord}.") legal_tile_ids = _legal_tile_ids_by_coord(get_game(g.game_name), coord) legal_tile_ids.sort(key=lambda tile_id: f"{game.tiles[tile_id].upgrade_level}-{tile_id:0>3}") LOG.info(f"Legal tiles response for {coord}: {legal_tile_ids}") return jsonify({"legal-tile-ids": legal_tile_ids})
def legal_token_coords(): game = get_game(g.game_name) private_companies = game.get_game_submodule("private_companies") if not private_companies: return jsonify({"coords": {}}) LOG.info("Legal private company token coordinates request.") private_company_coords = private_companies.PRIVATE_COMPANY_COORDS LOG.info( f"Legal private company token coordinates response: {private_company_coords}" ) return jsonify({"coords": private_company_coords})
def removable_railroads(): LOG.info("Removable railroads request.") removable_railroads = _get_removable_railroads() LOG.info(f"Removable railroads response: {removable_railroads}") railroads_info = get_railroad_info(g.game_name) return jsonify({ "railroads": list(sorted(removable_railroads)), "home-cities": { railroad: railroads_info[railroad]["home"] for railroad in removable_railroads } })
def closable_railroads(): LOG.info("Closable railroads request.") game = get_game(g.game_name) closable_railroads = _get_closable_railroads(game) LOG.info(f"Closable railroads response: {closable_railroads}") railroads_info = get_railroad_info(game) return jsonify({ "railroads": list(sorted(closable_railroads)), "home-cities": { railroad: railroads_info[railroad]["home"] for railroad in closable_railroads } })
def private_companies_open(): game = get_game(g.game_name) private_companies = game.get_game_submodule("private_companies") if not private_companies: return jsonify({"private-companies": []}) LOG.info("Open private companies request.") phase = _phase_from_trains(request.args.get("trains")) private_companies = [ private_company for private_company in private_companies.COMPANIES if not game.private_is_closed(private_company, phase) ] LOG.info(f"Open private companies response: {private_companies}") return jsonify({"private-companies": private_companies})
def legal_tile_coords(): LOG.info("Legal tile coordinates request.") current_coord = request.args.get("coord") existing_tile_coords = { coord for coord in json.loads(request.args.get("tile_coords")) if coord } legal_tile_coordinates = set(get_tile_coords(get_board( g.game_name))) - existing_tile_coords if current_coord: legal_tile_coordinates.add(current_coord) LOG.info(f"Legal tile coordinates response: {legal_tile_coordinates}") return jsonify({"tile-coords": list(sorted(legal_tile_coordinates))})
def cities(): LOG.info("Cities request.") board = get_board(g.game_name) # all_cities = [str(cell) for cell in sorted(board.cells) if board.get_space(cell) and board.get_space(cell).is_city] all_cities = [] split_cities = [] for cell in sorted(board.cells): space = board.get_space(cell) if space and space.is_city: coord = str(cell) all_cities.append(coord) if isinstance(space, (boardtile.SplitCity, placedtile.SplitCity)): split_cities.append(coord) LOG.info(f"Cities response: {all_cities}") return jsonify({"cities": all_cities, "split-cities": split_cities})
def legal_orientations(): coord = request.args.get("coord") tile_id = request.args.get("tileId") LOG.info(f"Legal orientations request for {tile_id} at {coord}.") orientations, translations = _get_orientations(get_game(g.game_name), coord, tile_id) LOG.info( f"Legal orientations response for {tile_id} at {coord}: {orientations}" ) return jsonify({ "legal-orientations": list(sorted(orientations)) if orientations is not None else orientations, "translations": translations })
def process_migrate_data(): migration_data = json.loads(session.pop(SESSION_COOKIE_KEY, "{}")) if migration_data: LOG.debug(f"Migration finalizing: found session data.") if _validate_migration_data(migration_data): LOG.debug(f"Migration data pre-conversion: {migration_data}") migration_data = _convert_migration_data(migration_data) LOG.debug(f"Migration data post-conversion: {migration_data}") else: LOG.debug( f"Migration validation failed: continuing without migration") migration_data = {} return migration_data
def legal_railroads(): LOG.info("Legal railroads request.") existing_railroads = { railroad for railroad in json.loads(request.args.get("railroads", "{}")) if railroad } railroads_info = get_railroad_info(g.game_name) legal_railroads = set(railroads_info.keys()) - existing_railroads LOG.info(f"Legal railroads response: {legal_railroads}") return jsonify({ "railroads": list(sorted(legal_railroads)), "home-cities": { railroad: railroads_info[railroad]["home"] for railroad in legal_railroads } })
def split_city_stations(): LOG.info("Legal split city stations request.") existing_station_coords = { coord for coord in json.loads(request.args.get("stations", "{}")) if coord } split_city_station_coords = _get_split_city_stations( # The default values can change once the templates are generalized. And # they'll need to before implementing another game with split cities. request.args["coord"], request.args.get("tileId"), request.args.get("orientation")) legal_stations = sorted(split_city_station_coords - existing_station_coords) LOG.info(f"Legal split city stations response: {legal_stations}") return jsonify({"split-city-stations": legal_stations})
def calculate_result(): routes_json = get_calculate_result(request.args.get("jobId")) LOG.info(f"Calculate response: {routes_json}") return jsonify(routes_json)
def calculate(): railroads_state_rows = json.loads(request.form.get("railroads-json")) removed_railroads = json.loads(request.form.get("removed-railroads-json")) closed_railroads = json.loads(request.form.get("closed-railroads-json")) private_companies_rows = json.loads(request.form.get("private-companies-json")) board_state_rows = json.loads(request.form.get("board-state-json")) railroad_name = request.form["railroad-name"] LOG.info("Calculate request.") LOG.info(f"Target railroad: {railroad_name}") LOG.info(f"Private companies: {private_companies_rows}") LOG.info(f"Railroad input: {railroads_state_rows}") LOG.info(f"Removed railroads: {removed_railroads}") LOG.info(f"Closed railroads: {closed_railroads}") LOG.info(f"Board input: {board_state_rows}") railroads_state_rows += [[name, "removed"] for name in removed_railroads] railroads_state_rows += [[name, "closed"] for name in closed_railroads] job = CALCULATOR_QUEUE.enqueue(calculate_worker, g.game_name, railroads_state_rows, private_companies_rows, board_state_rows, railroad_name, job_timeout="5m") return jsonify({"jobId": job.id})
def _validate_migration_data(migration_data): if not isinstance(migration_data, dict): LOG.debug( f"Migration validation failure: migration_data is not a dict") return {"error": "Failed to load migration data."} game = get_game("1846") railroad_info = get_railroad_info(game) private_companies = game.get_game_submodule("private_companies") for key, value in migration_data.items(): # Make sure no control characters or non-ASCII characters are present. if not key.isascii() or not key.isprintable() or not value.isascii( ) or not value.isprintable(): LOG.debug( f"Migration failure: key or value is not printable ASCII") return False try: migration_data[key] = json.dumps(json.loads(value)) except Exception as exc: LOG.debug( f"Migration validation failure[{key}]: value not valid JSON: {value}" ) return False if key == "placedTilesTable": tiles_json = json.loads(value) if not all(len(row) == 3 for row in tiles_json) or not all( str(col).isalnum() for row in tiles_json for col in row): LOG.debug(f"Migration validation failure[{key}]: {value}") return False elif key == "railroadsTable": railroads_json = json.loads(value) for row in railroads_json: if len(row) not in (3, 4) or row[0].strip() not in railroad_info: LOG.debug( f"Migration validation failure[{key}]: row wrong length, or railroad name invalid: {value}" ) return False if row[1] and row[1].strip(): train_strs = [ train_str.strip().split("/", 1) for train_str in row[1].strip().split(",") ] if not all(val.strip().isdigit() for train_str in train_strs for val in train_str): LOG.debug( f"Migration validation failure[{key}]: trains malformed: {value}" ) return False if row[2] and row[2].strip(): if not all(station_str.strip().isalnum() for station_str in row[2].strip().split(",")): LOG.debug( f"Migration validation failure[{key}]: stations malformed: {value}" ) return False elif key == "removedRailroadsTable": removed_railroads = json.loads(value) for railroad in removed_railroads: if railroad.strip() not in railroad_info: LOG.debug( f"Migration validation failure[{key}]: railroad name invalid: {value}" ) return False elif key == "privateCompaniesTable": private_companies_json = json.loads(value) for row in private_companies_json: if len(row) != 3 \ or row[0].strip() not in private_companies.COMPANIES \ or (row[1] and row[1].strip() not in railroad_info) \ or (row[2] and not row[2].strip().isalnum()): LOG.debug( f"Migration validation failure[{key}]: invalid row: {row}" ) return False elif key == "hideCityPaths": if value not in ("true", "false"): LOG.debug(f"Migration validation failure[{key}]: {value}") return False else: LOG.debug(f"Migration validation failure: invalid key: {key}") return False return True
def complete_migration(): LOG.info(f"Migration continued") id = request.args.get("id") if not id: LOG.debug(f"Migration failure: no ID provided") return redirect(url_for('.main')) try: uuid.UUID(id) except: LOG.debug(f"Migration failure: invalid ID provided; expected UUID") return redirect(url_for('.main')) LOG.info(f"Migration continuing for {id}") migration_data = redis_conn.hgetall(f"{REDIS_KEY_PREFIX}-{id}") if not migration_data: LOG.debug(f"Migration failure: no data to load") return redirect(url_for('.main')) # Once we get the data, we no longer need it, regardless of validity. redis_conn.delete(id) try: migration_data = { key.decode("ascii"): value.decode("ascii") for key, value in migration_data.items() } except UnicodeEncodeError as exc: LOG.debug(f"Migration failure: loaded data not all ASCII: {exc}") return redirect(url_for('.main')) if not _validate_migration_data(migration_data): return redirect(url_for('.main')) LOG.debug(f"Migration continuing: storing data in session") session[SESSION_COOKIE_KEY] = json.dumps(migration_data) return redirect(url_for('.main'))
def start_migration(): # The processing of migration_data is meant to disrupt an attacker trying # to poison the redis instance. As such, the error message should not give # them any details as to what went wrong. error_message = {"error": "Failed to start the migration."} LOG.info(f"Migration requested") migration_data = request.data if not migration_data: LOG.debug(f"Migration failure: no migration data provided") return jsonify(error_message), 400 migration_data = migration_data.decode("utf-8") if not migration_data.strip().isprintable() or not migration_data.strip( ).isascii(): LOG.debug(f"Migration failure: migration data not all printable ASCII") return jsonify(error_message), 400 try: migration_data_json = json.loads(migration_data) except Exception as exc: LOG.debug( f"Migration failure: migration data is not valid JSON: {exc}") return jsonify(error_message), 400 try: validation_error = _validate_migration_data(migration_data_json) except Exception as exc: LOG.debug( f"Migration failure: an exception occurred during validation: {exc}" ) return jsonify(error_message), 400 if not validation_error: return jsonify(error_message), 400 redis_id = str(uuid.uuid4()) redis_key = f"{REDIS_KEY_PREFIX}-{redis_id}" redis_conn.hmset(redis_key, migration_data_json) redis_conn.expire(redis_key, 60) LOG.info(f"Migration initiated: {redis_id}") return jsonify({"id": redis_id}), 201