From 1c37871bf85e8f1754345b9ea84ee76191e8478f Mon Sep 17 00:00:00 2001 From: Tulir Asokan <tulir@maunium.net> Date: Mon, 11 May 2020 23:48:03 +0300 Subject: [PATCH] Fix logging in with cookies and remove user agent stuff --- .../97e9f7a3c070_remove_user_agent_from_db.py | 26 +++++++++++ mautrix_facebook/commands/auth.py | 43 ++----------------- mautrix_facebook/db/user.py | 6 +-- mautrix_facebook/user.py | 18 +++----- mautrix_facebook/web/public.py | 13 +----- 5 files changed, 39 insertions(+), 67 deletions(-) create mode 100644 alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py diff --git a/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py b/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py new file mode 100644 index 0000000..d6d1600 --- /dev/null +++ b/alembic/versions/97e9f7a3c070_remove_user_agent_from_db.py @@ -0,0 +1,26 @@ +"""Remove user agent from db + +Revision ID: 97e9f7a3c070 +Revises: c56c9a30b228 +Create Date: 2020-05-11 23:40:17.319060 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '97e9f7a3c070' +down_revision = 'c56c9a30b228' +branch_labels = None +depends_on = None + + +def upgrade(): + with op.batch_alter_table("user") as batch_op: + batch_op.drop_column('user_agent') + + +def downgrade(): + with op.batch_alter_table("user") as batch_op: + batch_op.add_column(sa.Column('user_agent', sa.String(length=255), autoincrement=False, nullable=True)) diff --git a/mautrix_facebook/commands/auth.py b/mautrix_facebook/commands/auth.py index 2353e3c..624ce7b 100644 --- a/mautrix_facebook/commands/auth.py +++ b/mautrix_facebook/commands/auth.py @@ -14,10 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. import asyncio -import random import time -from http.cookies import SimpleCookie from yarl import URL import fbchat @@ -28,22 +26,6 @@ from .. import puppet as pu from mautrix.bridge import custom_puppet as cpu from . import command_handler, CommandEvent, SECTION_AUTH -USER_AGENTS = [ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" - " Chrome/74.0.3729.169 Safari/537.36", - - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0", - - # "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like" - # " Gecko) Version/12.1.1 Mobile/15E148 Safari/604.1", - - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0", - - # "Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 - # (KHTML," - # " like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36", -] - @command_handler(needs_auth=False, management_only=True, help_section=SECTION_AUTH, help_text="Log in to Facebook", @@ -59,8 +41,6 @@ async def login(evt: CommandEvent) -> None: "action": "Login", "room_id": evt.room_id, } - if not evt.sender.user_agent: - evt.sender.user_agent = random.choice(USER_AGENTS) await evt.reply("Logging in...") try: session = await fbchat.Session.login(evt.args[0], " ".join(evt.args[1:]), @@ -74,18 +54,6 @@ async def login(evt: CommandEvent) -> None: evt.log.exception("Failed to log in") -@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 again or on the next bridge restart.") - - async def enter_2fa_code(evt: CommandEvent) -> None: code = " ".join(evt.args) future: asyncio.Future = evt.sender.command_status["future"] @@ -150,14 +118,11 @@ async def enter_login_cookies(evt: CommandEvent) -> None: "the `cancel` command to cancel.") return - if not evt.sender.user_agent: - evt.sender.user_agent = random.choice(USER_AGENTS) - - cookie = SimpleCookie() - cookie["c_user"] = evt.sender.command_status["c_user"] - cookie["xs"] = evt.args[0] try: - session = await fbchat.Session.from_cookies(cookie) + session = await fbchat.Session.from_cookies({ + "c_user": evt.sender.command_status["c_user"], + "xs": evt.args[0], + }) except fbchat.FacebookError as e: evt.sender.command_status = None await evt.reply(f"Failed to log in: {e}") diff --git a/mautrix_facebook/db/user.py b/mautrix_facebook/db/user.py index 7fb66bb..f1ebe8e 100644 --- a/mautrix_facebook/db/user.py +++ b/mautrix_facebook/db/user.py @@ -13,8 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. -from typing import Optional, Iterable -from http.cookies import SimpleCookie +from typing import Optional, Iterable, Dict from sqlalchemy import Column, String, PickleType @@ -26,9 +25,8 @@ class User(Base): __tablename__ = "user" mxid: UserID = Column(String(255), primary_key=True) - session: SimpleCookie = Column(PickleType, nullable=True) + session: Dict[str, str] = Column(PickleType, nullable=True) fbid: str = 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 ef0db5a..c692856 100644 --- a/mautrix_facebook/user.py +++ b/mautrix_facebook/user.py @@ -15,7 +15,6 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. from typing import (Any, Dict, Iterator, Optional, Iterable, Type, Callable, Awaitable, Union, TYPE_CHECKING) -from http.cookies import SimpleCookie import asyncio import logging @@ -47,24 +46,22 @@ class User: client: Optional[fbchat.Client] listener: Optional[fbchat.Listener] listen_task: Optional[asyncio.Task] - user_agent: str command_status: Optional[Dict[str, Any]] is_whitelisted: bool is_admin: bool permission_level: str _is_logged_in: Optional[bool] - _session_data: Optional[SimpleCookie] + _session_data: Optional[Dict[str, str]] _db_instance: Optional[DBUser] _community_helper: CommunityHelper _community_id: Optional[CommunityID] - def __init__(self, mxid: UserID, session: Optional[SimpleCookie] = None, - user_agent: Optional[str] = None, db_instance: Optional[DBUser] = None) -> None: + def __init__(self, mxid: UserID, session: Optional[Dict[str, str]] = None, + db_instance: Optional[DBUser] = None) -> None: self.mxid = mxid 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 @@ -90,21 +87,18 @@ class User: @property def db_instance(self) -> DBUser: if not self._db_instance: - self._db_instance = DBUser(mxid=self.mxid, session=self._session_data, - fbid=self.fbid, user_agent=self.user_agent) + self._db_instance = DBUser(mxid=self.mxid, session=self._session_data, fbid=self.fbid) return self._db_instance def save(self, _update_session_data: bool = True) -> None: self.log.debug("Saving session") 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, - user_agent=self.user_agent) + self.db_instance.edit(session=self._session_data, fbid=self.fbid) @classmethod def from_db(cls, db_user: DBUser) -> 'User': - return User(mxid=db_user.mxid, session=db_user.session, user_agent=db_user.user_agent, - db_instance=db_user) + return User(mxid=db_user.mxid, session=db_user.session, db_instance=db_user) @classmethod def get_all(cls) -> Iterator['User']: diff --git a/mautrix_facebook/web/public.py b/mautrix_facebook/web/public.py index fa2ce81..2d74173 100644 --- a/mautrix_facebook/web/public.py +++ b/mautrix_facebook/web/public.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. from typing import Optional, Dict -from http.cookies import SimpleCookie import logging import random import string @@ -113,23 +112,13 @@ class PublicBridgeWebsite: async def login(self, request: web.Request) -> web.Response: user = self.check_token(request) - try: - user_agent = request.headers["User-Agent"] - except KeyError: - raise web.HTTPBadRequest(body='{"error": "Missing User-Agent header"}', - headers=self._headers) try: data = await request.json() except json.JSONDecodeError: raise web.HTTPBadRequest(body='{"error": "Malformed JSON"}', headers=self._headers) - cookie = SimpleCookie() - cookie["c_user"] = data["c_user"] - cookie["xs"] = data["xs"] - user.user_agent = user_agent - user.save() try: - session = await fbchat.Session.from_cookies(cookie) + session = await fbchat.Session.from_cookies(data) except fbchat.FacebookError: self.log.debug("Failed to log in", exc_info=True) raise web.HTTPUnauthorized(body='{"error": "Facebook authorization failed"}', -- GitLab