From cefeae9d680c0ac87bd60ab6e2824e778dabc640 Mon Sep 17 00:00:00 2001 From: Tulir Asokan <tulir@maunium.net> Date: Tue, 30 Jun 2020 15:14:54 +0300 Subject: [PATCH] Re-add option to set user agent --- .../244273a86d81_re_add_user_agent_to_db.py | 32 +++++++++++++++++++ mautrix_facebook/commands/auth.py | 17 ++++++++-- mautrix_facebook/db/user.py | 3 +- mautrix_facebook/user.py | 15 +++++---- mautrix_facebook/web/public.py | 5 ++- requirements.txt | 2 +- 6 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 alembic/versions/244273a86d81_re_add_user_agent_to_db.py diff --git a/alembic/versions/244273a86d81_re_add_user_agent_to_db.py b/alembic/versions/244273a86d81_re_add_user_agent_to_db.py new file mode 100644 index 0000000..8eef779 --- /dev/null +++ b/alembic/versions/244273a86d81_re_add_user_agent_to_db.py @@ -0,0 +1,32 @@ +"""Re-add user agent to db + +Revision ID: 244273a86d81 +Revises: 54aecbd6187c +Create Date: 2020-06-30 15:03:18.260742 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '244273a86d81' +down_revision = '54aecbd6187c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('user_agent', sa.String(length=255), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_column('user_agent') + + # ### end Alembic commands ### diff --git a/mautrix_facebook/commands/auth.py b/mautrix_facebook/commands/auth.py index bd9f0a4..60ebc0b 100644 --- a/mautrix_facebook/commands/auth.py +++ b/mautrix_facebook/commands/auth.py @@ -44,7 +44,8 @@ async def login(evt: CommandEvent) -> None: await evt.reply("Logging in...") try: session = await fbchat.Session.login(evt.args[0], " ".join(evt.args[1:]), - on_2fa_callback=evt.sender.on_2fa_callback) + on_2fa_callback=evt.sender.on_2fa_callback, + user_agent=evt.sender.user_agent) await evt.sender.on_logged_in(session) evt.sender.command_status = None await evt.reply("Successfully logged in") @@ -123,7 +124,7 @@ async def enter_login_cookies(evt: CommandEvent) -> None: session = await fbchat.Session.from_cookies({ "c_user": evt.sender.command_status["c_user"], "xs": evt.args[0], - }) + }, user_agent=evt.sender.user_agent) except fbchat.FacebookError as e: evt.sender.command_status = None await evt.reply(f"Failed to log in: {e}") @@ -138,6 +139,18 @@ async def enter_login_cookies(evt: CommandEvent) -> None: evt.sender.command_status = None +@command_handler(needs_auth=False, management_only=False, help_section=SECTION_AUTH, + help_text="Change the user agent sent to Facebook", help_args="<_user agent_>") +async def set_ua(evt: CommandEvent) -> None: + if len(evt.args) < 0: + await evt.reply("Usage: `$cmdprefix+sp login <user agent>`") + return + evt.sender.user_agent = " ".join(evt.args) + evt.sender.save() + await evt.reply(f"Set user agent to `{evt.sender.user_agent}`. The change will be applied when" + " you log in, run `refresh`, or on the next bridge restart.") + + @command_handler(needs_auth=True, help_section=SECTION_AUTH, help_text="Log out of Facebook") async def logout(evt: CommandEvent) -> None: puppet = pu.Puppet.get_by_fbid(evt.sender.fbid) diff --git a/mautrix_facebook/db/user.py b/mautrix_facebook/db/user.py index 90089aa..92aadc3 100644 --- a/mautrix_facebook/db/user.py +++ b/mautrix_facebook/db/user.py @@ -1,5 +1,5 @@ # mautrix-facebook - A Matrix-Facebook Messenger puppeting bridge -# Copyright (C) 2019 Tulir Asokan +# Copyright (C) 2020 Tulir Asokan # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -28,6 +28,7 @@ class User(Base): session: Dict[str, str] = Column(PickleType, nullable=True) fbid: str = Column(String(255), nullable=True) notice_room: RoomID = Column(String(255), nullable=True) + user_agent: str = Column(String(255), nullable=True) @classmethod def all(cls) -> Iterable['User']: diff --git a/mautrix_facebook/user.py b/mautrix_facebook/user.py index f0e8e6d..87ae1bf 100644 --- a/mautrix_facebook/user.py +++ b/mautrix_facebook/user.py @@ -51,6 +51,7 @@ class User(BaseUser): notice_room: RoomID _notice_room_lock: asyncio.Lock _notice_send_lock: asyncio.Lock + user_agent: Optional[str] is_admin: bool permission_level: str _is_logged_in: Optional[bool] @@ -69,13 +70,14 @@ class User(BaseUser): _handlers: Dict[Type[fbchat.Event], Callable[[Any], Awaitable[None]]] def __init__(self, mxid: UserID, session: Optional[Dict[str, str]] = None, - notice_room: Optional[RoomID] = None, + notice_room: Optional[RoomID] = None, user_agent: Optional[str] = None, db_instance: Optional[DBUser] = None) -> None: self.mxid = mxid + self.by_mxid[mxid] = self self.notice_room = notice_room self._notice_room_lock = asyncio.Lock() self._notice_send_lock = asyncio.Lock() - self.by_mxid[mxid] = self + self.user_agent = user_agent self.command_status = None self.is_whitelisted, self.is_admin, self.permission_level = config.get_permissions(mxid) self._is_logged_in = None @@ -135,7 +137,7 @@ class User(BaseUser): def db_instance(self) -> DBUser: if not self._db_instance: self._db_instance = DBUser(mxid=self.mxid, session=self._session_data, fbid=self.fbid, - notice_room=self.notice_room) + notice_room=self.notice_room, user_agent=self.user_agent) return self._db_instance def save(self, _update_session_data: bool = True) -> None: @@ -143,11 +145,11 @@ class User(BaseUser): if _update_session_data and self.session: self._session_data = self.session.get_cookies() self.db_instance.edit(session=self._session_data, fbid=self.fbid, - notice_room=self.notice_room) + notice_room=self.notice_room, user_agent=self.user_agent) @classmethod def from_db(cls, db_user: DBUser) -> 'User': - return User(mxid=db_user.mxid, session=db_user.session, + return User(mxid=db_user.mxid, session=db_user.session, user_agent=db_user.user_agent, notice_room=db_user.notice_room, db_instance=db_user) @classmethod @@ -194,7 +196,8 @@ class User(BaseUser): elif not self._session_data: return False try: - session = await fbchat.Session.from_cookies(self._session_data) + session = await fbchat.Session.from_cookies(self._session_data, + user_agent=self.user_agent) logged_in = await session.is_logged_in() except Exception: self.log.exception("Failed to restore session") diff --git a/mautrix_facebook/web/public.py b/mautrix_facebook/web/public.py index 2724c92..926301e 100644 --- a/mautrix_facebook/web/public.py +++ b/mautrix_facebook/web/public.py @@ -102,6 +102,7 @@ class PublicBridgeWebsite: data = { "permissions": user.permission_level, "mxid": user.mxid, + "user_agent": user.user_agent, "facebook": None, } if await user.is_logged_in(): @@ -112,6 +113,8 @@ class PublicBridgeWebsite: async def login(self, request: web.Request) -> web.Response: user = self.check_token(request) + if not user.user_agent: + user.user_agent = request.headers.get("User-Agent", None) try: data = await request.json() @@ -119,7 +122,7 @@ class PublicBridgeWebsite: raise web.HTTPBadRequest(body='{"error": "Malformed JSON"}', headers=self._headers) try: - session = await fbchat.Session.from_cookies(data) + session = await fbchat.Session.from_cookies(data, user_agent=user.user_agent) except fbchat.FacebookError: self.log.debug("Failed to log in", exc_info=True) raise web.HTTPUnauthorized(body='{"error": "Facebook authorization failed"}', diff --git a/requirements.txt b/requirements.txt index 195fc07..485161f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ ruamel.yaml>=0.15.94,<0.17 commonmark>=0.8,<0.10 python-magic>=0.4,<0.5 mautrix==0.6.0.alpha2 -fbchat-asyncio>=0.6.4,<0.7 +fbchat-asyncio>=0.6.6,<0.7 -- GitLab