import json import logging import os import webbrowser from http.server import BaseHTTPRequestHandler, HTTPServer from authlib.common.security import generate_token from authlib.integrations.requests_client import OAuth2Session from PyViCare.PyViCareAbstractOAuthManager import ( AUTHORIZE_URL, SCOPE_IOT, SCOPE_OFFLINE_ACCESS, SCOPE_USER, TOKEN_URL, AbstractViCareOAuthManager, ) from PyViCare.PyViCareUtils import (PyViCareBrowserOAuthTimeoutReachedError, PyViCareInvalidCredentialsError) logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) REDIRECT_PORT = 51125 AUTH_TIMEOUT = 60 * 3 class ViCareBrowserOAuthManager(AbstractViCareOAuthManager): class Serv(BaseHTTPRequestHandler): def __init__(self, callback, *args): self.callback = callback BaseHTTPRequestHandler.__init__(self, *args) def do_GET(self): (status_code, text) = self.callback(self.path) self.send_response(status_code) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(text.encode("utf-8")) def __init__(self, client_id: str, token_file: str) -> None: self.token_file = token_file self.client_id = client_id oauth_session = self.__load_or_create_new_session() super().__init__(oauth_session) def __load_or_create_new_session(self): restore_oauth = self.__restoreToken() if restore_oauth is not None: return restore_oauth return self.__execute_browser_authentication() def __execute_browser_authentication(self): redirect_uri = f"http://localhost:{REDIRECT_PORT}" oauth_session = OAuth2Session( self.client_id, redirect_uri=redirect_uri, scope=[SCOPE_IOT, SCOPE_USER, SCOPE_OFFLINE_ACCESS], code_challenge_method='S256') code_verifier = generate_token(48) authorization_url, _ = oauth_session.create_authorization_url(AUTHORIZE_URL, code_verifier=code_verifier) webbrowser.open(authorization_url) location = None def callback(path): nonlocal location location = path return (200, "Success. You can close this browser window now.") def handlerWithCallbackWrapper(*args): ViCareBrowserOAuthManager.Serv(callback, *args) server = HTTPServer(('localhost', REDIRECT_PORT), handlerWithCallbackWrapper) server.timeout = AUTH_TIMEOUT server.handle_request() if location is None: logger.debug("Timeout reached") raise PyViCareBrowserOAuthTimeoutReachedError() logger.debug("Location: %s", location) oauth_session.fetch_token(TOKEN_URL, authorization_response=location, code_verifier=code_verifier) if oauth_session.token is None: raise PyViCareInvalidCredentialsError() logger.debug("Token received: %s", oauth_session.token) self.__storeToken(oauth_session.token) logger.info("New token created") return oauth_session def __storeToken(self, token): if self.token_file is None: return with open(self.token_file, mode='w') as json_file: json.dump(token, json_file) logger.info("Token stored to file") def __restoreToken(self): if self.token_file is None or not os.path.isfile(self.token_file): return None with open(self.token_file, mode='r') as json_file: token = json.load(json_file) logger.info("Token restored from file") return OAuth2Session(self.client_id, token=token) def renewToken(self) -> None: # type: ignore refresh_token = self.oauth_session.refresh_token self.oauth_session.refresh_token(TOKEN_URL, refresh_token=refresh_token)