def _handler(**kwargs): try: user: User if not self.app.debug: user = User.get( discord_id=self.discord.fetch_user().id) if user is None or not user.admin: return "User not authorized", 403 else: user = list(User.select().limit(1))[0] return handler(user, **kwargs) except flask_discord.exceptions.Unauthorized: return "Unknown user", 404
def login_with_guest(sio: ServerApp, encrypted_login_request: bytes): if sio.guest_encrypt is None: raise NotAuthorizedForAction() try: login_request_bytes = sio.guest_encrypt.decrypt(encrypted_login_request) except cryptography.fernet.InvalidToken: raise NotAuthorizedForAction() try: login_request = json.loads(login_request_bytes.decode("utf-8")) name = login_request["name"] date = datetime.datetime.fromisoformat(login_request["date"]) except (UnicodeDecodeError, json.JSONDecodeError, KeyError, ValueError) as e: raise InvalidAction(str(e)) if _get_now() - date > datetime.timedelta(days=1): raise NotAuthorizedForAction() user: User = User.create(name=f"Guest: {name}") with sio.session() as session: session["user-id"] = user.id return _create_client_side_session(sio, user)
def test_login_with_guest(flask_app, clean_database, mocker): # Setup mocker.patch("randovania.server.user_session._get_now", return_value=datetime.datetime(year=2020, month=9, day=4)) mock_create_session = mocker.patch( "randovania.server.user_session._create_client_side_session", autospec=True) enc_request = b"encrypted stuff" sio = MagicMock() sio.guest_encrypt.decrypt.return_value = json.dumps({ "name": "Someone", "date": '2020-09-05T17:12:09.941661', }).encode("utf-8") with flask_app.test_request_context(): flask.request.sid = 7890 result = user_session.login_with_guest(sio, enc_request) # Assert sio.guest_encrypt.decrypt.assert_called_once_with(enc_request) user: User = User.get_by_id(1) assert user.name == "Guest: Someone" mock_create_session.assert_called_once_with(sio, user) assert result is mock_create_session.return_value
def get_current_user(self) -> User: try: return User.get_by_id(self.get_session()["user-id"]) except KeyError: raise NotLoggedIn() except peewee.DoesNotExist: raise InvalidSession()
def _create_client_side_session(sio: ServerApp, user: Optional[User]) -> dict: """ :param user: If the session's user was already retrieved, pass it along to avoid an extra query. :return: """ session = sio.get_session() encrypted_session = sio.fernet_encrypt.encrypt( json.dumps(session).encode("utf-8")) if user is None: user = User.get_by_id(session["user-id"]) elif user.id != session["user-id"]: raise RuntimeError(f"Provided user does not match the session's user") logger().info( f"Client at {sio.current_client_ip()} is user {user.name} ({user.id})." ) return { "user": user.as_json, "sessions": [ membership.session.create_list_entry() for membership in GameSessionMembership.select().where( GameSessionMembership.user == user) ], "encoded_session_b85": base64.b85encode(encrypted_session), }
def restore_user_session(sio: ServerApp, encrypted_session: bytes, session_id: Optional[int]): try: decrypted_session: bytes = sio.fernet_encrypt.decrypt( encrypted_session) session = json.loads(decrypted_session.decode("utf-8")) if "discord-access-token" in session: user, result = _create_session_with_discord_token( sio, session["discord-access-token"]) else: user = User.get_by_id(session["user-id"]) sio.save_session(session) result = _create_client_side_session(sio, user) if session_id is not None: sio.join_game_session( GameSessionMembership.get_by_ids(user.id, session_id)) return result except UserNotAuthorized: raise except (KeyError, peewee.DoesNotExist, json.JSONDecodeError, InvalidTokenError) as e: # InvalidTokenError: discord token expired and couldn't renew logger().info("Client at %s was unable to restore session: (%s) %s", sio.current_client_ip(), str(type(e)), str(e)) raise InvalidSession() except Exception: logger().exception("Error decoding user session") raise InvalidSession()
def login_with_discord(sio: ServerApp, code: str): oauth = OAuth2Session( client_id=sio.app.config["DISCORD_CLIENT_ID"], scope=["identify"], redirect_uri=sio.app.config["DISCORD_REDIRECT_URI"], ) access_token = oauth.fetch_token( "https://discord.com/api/oauth2/token", code=code, client_secret=sio.app.config["DISCORD_CLIENT_SECRET"], ) flask.session["DISCORD_OAUTH2_TOKEN"] = access_token discord_user = sio.discord.fetch_user() user: User = User.get_or_create(discord_id=discord_user.id, defaults={"name": discord_user.name})[0] if user.name != discord_user.name: user.name = discord_user.name user.save() with sio.session() as session: session["user-id"] = user.id session["discord-access-token"] = access_token return _create_client_side_session(sio, user)
def restore_user_session(sio: ServerApp, encrypted_session: bytes, session_id: Optional[int]): try: decrypted_session: bytes = sio.fernet_encrypt.decrypt( encrypted_session) session = json.loads(decrypted_session.decode("utf-8")) user = User.get_by_id(session["user-id"]) if "discord-access-token" in session: # TODO: test if the discord access token is still valid flask.session["DISCORD_OAUTH2_TOKEN"] = session[ "discord-access-token"] sio.get_server().save_session(flask.request.sid, session) if session_id is not None: sio.join_game_session( GameSessionMembership.get_by_ids(user.id, session_id)) return _create_client_side_session(sio, user) except (KeyError, peewee.DoesNotExist, json.JSONDecodeError): raise InvalidSession() except Exception: logger().exception("Error decoding user session") raise InvalidSession()
def browser_discord_login_callback(): sio.discord.callback() discord_user = sio.discord.fetch_user() user: User = User.get(discord_id=discord_user.id) if user is None: return "Unknown user", 404 return flask.redirect(flask.url_for("browser_me"))
def _handler(**kwargs): try: user: User = User.get( discord_id=self.discord.fetch_user().id) if user is None or not user.admin: return "User not authorized", 403 return handler(user, **kwargs) except flask_discord.exceptions.Unauthorized: return "Unknown user", 404
def test_login_with_discord(mock_fetch_token: MagicMock, clean_database, flask_app, existing): # Setup sio = MagicMock() session = {} sio.session.return_value.__enter__.return_value = session sio.get_session.return_value = session mock_fetch_token.return_value = "access_token" sio.fernet_encrypt.encrypt.return_value = b"encrypted" discord_user = sio.discord.fetch_user.return_value discord_user.id = 1234 discord_user.name = "A Name" if existing: User.create(discord_id=discord_user.id, name="Someone else") # Run with flask_app.test_request_context(): result = user_session.login_with_discord(sio, "code") # Assert mock_fetch_token.assert_called_once_with( ANY, "https://discord.com/api/oauth2/token", code="code", client_secret=sio.app.config["DISCORD_CLIENT_SECRET"], ) user = User.get(User.discord_id == 1234) assert user.name == "A Name" assert session == { "user-id": user.id, "discord-access-token": "access_token", } assert result == { "user": user.as_json, "sessions": [], "encoded_session_b85": b'Wo~0~d2n=PWB', }
def _create_session_with_discord_token(sio: ServerApp, access_token: str) -> Tuple[User, dict]: flask.session["DISCORD_OAUTH2_TOKEN"] = access_token discord_user = sio.discord.fetch_user() user: User = User.get_or_create(discord_id=discord_user.id, defaults={"name": discord_user.name})[0] if user.name != discord_user.name: user.name = discord_user.name user.save() if sio.enforce_role is not None: if not sio.enforce_role.verify_user(discord_user.id): logger().info( "User %s is not authorized for connecting to the server", discord_user.name) raise UserNotAuthorized() with sio.session() as session: session["user-id"] = user.id session["discord-access-token"] = access_token return user, _create_client_side_session(sio, user)
def test_restore_user_session_with_discord(mock_create_session: MagicMock, flask_app, fernet, clean_database): sio = MagicMock() sio.fernet_encrypt = fernet user: User = User.create(id=1234, discord_id=5678, name="The Name") session = { "user-id": 1234, "discord-access-token": "access-token", } enc_session = fernet.encrypt(json.dumps(session).encode("utf-8")) # Run with flask_app.test_request_context(): flask.request.sid = 7890 result = user_session.restore_user_session(sio, enc_session, None) # Assert sio.get_server.return_value.save_session.assert_called_once_with( 7890, session) mock_create_session.assert_called_once_with(sio, user) assert result is mock_create_session.return_value