# Copyright (C) 2022 Milosz Piglas # # 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 the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from json import loads from random import randint from shutil import which from subprocess import Popen from threading import Lock, Thread from urllib.parse import urlparse, parse_qs from urllib.request import urlopen import logging LOG = logging.getLogger(__name__) LOG.addHandler(logging.NullHandler()) class AuthException(Exception): def __init__(self, msg): Exception.__init__(self, msg) class Process: def __init__( self, launch_uri, name="chrome", incognito=False, temp_profile=False, port=None ): self.proc_args = [which(name)] if incognito: self.proc_args.append("--incognito") if temp_profile: self.proc_args.append("--temp-profile") self.debug_port = port if port is None: self.debug_port = randint(2000, 4000) self.proc_args.append("--remote-debugging-port=" + str(self.debug_port)) self.proc_args.append(launch_uri) def _start(self): self._proc = Popen(self.proc_args) LOG.debug('Starting browser ' + str(self.proc_args)) self._proc.communicate() if self.lock.locked(): LOG.warn('Browser closed by user') self.lock.release() def launch(self): self.lock = Lock() self.lock.acquire(False) self._t = Thread(target=self._start) self._t.start() return self.lock def stop(self): LOG.debug('Terminating browser') self.lock.release() self._proc.terminate() def _wait_redirect(target, debug_port, lock): redirect = None while not redirect and lock.locked(): try: uri = "http://127.0.0.1:{}/json".format(debug_port) resp = _http_get(uri) obj = loads(resp.decode("utf-8")) for elem in obj: elem_type = elem.get("type", None) elem_url = elem.get("url", "") if elem_type == "page" and elem_url.startswith(target): redirect = elem_url except Exception as e: continue else: return redirect def _get_access_code(app_id, browser, redirect, port, incognito, temp_profile): login_uri = "https://www.facebook.com/v15.0/dialog/oauth?client_id={}&redirect_uri={}&state=get_login".format( app_id, redirect ) browser = Process(login_uri, browser, incognito, temp_profile, port) lock = browser.launch() access = _wait_redirect(redirect, browser.debug_port, lock) browser.stop() if access: access_url = urlparse(access) query = parse_qs(access_url.query) return query.get("code", [None])[0] else: raise AuthException("Authentication failed") def _http_get(uri): with urlopen(uri) as sock: return sock.read(4096) def get_standalone( app_id, app_secret, browser="chrome", redirect="https://example.com", port=None, incognito=False, temp_profile=False, ): """ Performs authentication and obtains access token to Facebook Graph API Function is intended for standalone applications, that integrate with Facebook Graph API. It opens web browser with Facebook login form. After successful authentication function reads code from redirect url and obtains access token. Depending on settings of Facebook aplication, a token can be used for requests to Meta services, that support Graph API. Function requires Facebook Application with activated Facebook Login. Parameters: app_id: Facebook Application Id app_secret: Facebook Application secret code browser: web browser, that is used for displaying login form redirect: URL to which web browser is redirected after successful user authentication port: Chrome debug port. If None then random port is selected incognito: if True then Chrome starts in incognito mode temp_profile: if True then Chrome starts with temporary profile Returns: JSON object, that represents Facebook Authentication Token Throws: AuthException if authentication fails from any reason """ code = _get_access_code(app_id, browser, redirect, port, incognito, temp_profile) token_uri = "https://graph.facebook.com/v15.0/oauth/access_token?client_id={}&client_secret={}&code={}&redirect_uri={}/".format( app_id, app_secret, code, redirect ) return loads(_http_get(token_uri)) if __name__ == "__main__": try: from getpass import getpass app_id = input("App ID: ") secret = getpass("App secret: ") t = get_standalone(app_id, secret, "chromium") print(t) except Exception as e: print(e.read(4096))