Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firefox cookies from given container in Multi-Account Containers #205

Open
rez-spb opened this issue Apr 18, 2024 · 6 comments
Open

Firefox cookies from given container in Multi-Account Containers #205

rez-spb opened this issue Apr 18, 2024 · 6 comments

Comments

@rez-spb
Copy link

rez-spb commented Apr 18, 2024

How does this module handle Multi-Account Containers in Firefox?

From what I've read (http://justsolve.archiveteam.org/wiki/Firefox_cookie_database), FF introduced originAttributes column since August, 2022, which I see present in SQLite DB.
Example value for this DB column: ^userContextId=1&firstPartyDomain=somedomain.com
I have several containers with different userContextId used for one site/host but with different credentials and cookies.

Is it possible for this module to address only cookies in particular container?
Currently it looks like it takes arbitrary one and my cookies are a mess: some fields are taken from one container, others may be from another, and it seems that there's no way to separate them due to internal CookieJar object having no originAttributes field (searched in code for this, didn't find it).

Usecase:

  • enable Multi-Account Containers;
  • use/create 2 additional containers in addition to default container;
  • login to Google in both created containers using different credentials;
  • leave default container without auth;
  • get cookies with this module.

Actual result:
Get one CookieJar object with randomly selected container and fields.

Expected result:
Get 3 objects with IDs for each container (including default) totally separated from one another with proper cookies for each.

Ability to select from returned objects by ID would also be much appreciated.
Hope I didn't miss something, because there's little documentation so I had to to try and err on this one.

@rez-spb
Copy link
Author

rez-spb commented Apr 19, 2024

I have solved this myself for my usecase.
Is it OK if I fork and send pull request or is this functionality not important enough? Just asking.

@Vetches
Copy link

Vetches commented Jul 6, 2024

Hi @rez-spb! Can you share your solution for extracting cookies from Firefox containers? I haven't been able to find a way to do so myself as yet! Thank you so much!

@NotDday
Copy link

NotDday commented Aug 13, 2024

I also have the same issue

@mbafford
Copy link

mbafford commented Nov 3, 2024

This extension of the Firefox cookies class works for me - it'd be better merged into the library, but at the moment I'm not able to do a proper PR - maybe someone else wants to run with this:

class FirefoxUserSpecific(browser_cookie3.Firefox):
    def __init__(self, domain_name: str, user_context_id: int):
        self.user_context_id = user_context_id
        super().__init__(domain_name=domain_name)

    # Monkey patch the FirefoxBased.load method to customize cookie loading
    def load(self, *args, **kwargs):
        import http.cookiejar

        cj = http.cookiejar.CookieJar()

        # firefoxbased seems faster with legacy mode
        with browser_cookie3._DatabaseConnetion(self.cookie_file, True) as con:
            cur = con.cursor()
            cur.execute('select host, path, isSecure, expiry, name, value, isHttpOnly from moz_cookies '
                        f'where host like ? and originAttributes like "%^userContextID={self.user_context_id}%"', ('%{}%'.format(self.domain_name),))

            all = cur.fetchall();

            for item in all:
                host, path, secure, expires, name, value, http_only = item
                c = browser_cookie3.create_cookie(host, path, secure, expires,
                                    name, value, http_only)            
                cj.set_cookie(c)

        # the relevant file doesn't exist in my testing - may need to support this
        # getattr(self, '_FirefoxBased__add_session_cookies')(cj)

        self.add_session_cookies_lz4(cj)

        return cj

    def add_session_cookies_lz4(self, cj):
        import lz4.block

        if not os.path.exists(self.session_file_lz4):
            return
        try:
            with open(self.session_file_lz4, 'rb') as file_obj:
                file_obj.read(8)
                json_data = json.loads(lz4.block.decompress(file_obj.read()))
        except ValueError as e:
            print(
                f'Error parsing {self.browser_name} session JSON LZ4:', str(e))
        else:
            for cookie in json_data.get('cookies', []):
                if self.domain_name == '' or self.domain_name in cookie.get('host', ''):
                    if cookie['originAttributes']['userContextId'] == self.user_context_id:
                        cj.set_cookie(getattr(self, '_FirefoxBased__create_session_cookie')(cookie))

@rez-spb
Copy link
Author

rez-spb commented Nov 4, 2024

@Vetches, sorry I have missed the reply in July. My bad.
My version is similar to @mbafford's, but with 3 differences: it addresses the container by id, checks whether user has container_id for compatibility and doesn't touch lz4 at all.

class FirefoxBased(browser_cookie3.FirefoxBased):
    def __init__(self, browser_name, cookie_file=None, domain_name="", container_id=None, **kwargs):
        super().__init__(browser_name, cookie_file, domain_name, **kwargs)
        self.container_id = container_id

    def load(self):
        cj = http.cookiejar.CookieJar()
        with browser_cookie3._DatabaseConnetion(self.cookie_file, True) as con:
            cur = con.cursor()
            container_id = self.container_id
            if container_id:
                cur.execute('select host, path, isSecure, expiry, name, value, isHttpOnly, originAttributes from moz_cookies '
                            'where host like ? and originAttributes like ?', ('%{}%'.format(self.domain_name),
                                                                              '^userContextId={}&%'.format(container_id)))
                for item in cur.fetchall():
                    host, path, secure, expires, name, value, http_only, origin = item
                    c = browser_cookie3.create_cookie(host, path, secure, expires, name, value, http_only)
                    cj.set_cookie(c)
            else:
                cur.execute(
                    'select host, path, isSecure, expiry, name, value, isHttpOnly from moz_cookies '
                    'where host like ?', ('%{}%'.format(self.domain_name),))
                for item in cur.fetchall():
                    host, path, secure, expires, name, value, http_only = item
                    c = browser_cookie3.create_cookie(host, path, secure, expires, name, value, http_only)
                    cj.set_cookie(c)

        self.__add_session_cookies(cj)
        self.__add_session_cookies_lz4(cj)

        return cj


class Firefox(FirefoxBased):
    """Class for Firefox"""

    def __init__(self, cookie_file=None, domain_name="", container_id=None):
        args = {
            'linux_data_dirs': [
                '~/snap/firefox/common/.mozilla/firefox',
                '~/.mozilla/firefox'
            ],
            'windows_data_dirs': [
                {'env': 'APPDATA', 'path': r'Mozilla\Firefox'},
                {'env': 'LOCALAPPDATA', 'path': r'Mozilla\Firefox'}
            ],
            'osx_data_dirs': [
                '~/Library/Application Support/Firefox'
            ]
        }
        super().__init__('Firefox', cookie_file, domain_name, container_id, **args)

Used in internal projects this way:

cj = Firefox(domain_name='example.com', container_id=666).load()

@Vetches
Copy link

Vetches commented Nov 5, 2024

Thank you both so, so much for taking the time to reply, this is incredibly helpful!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants