diff --git a/zulip/integrations/google/get-google-credentials b/zulip/integrations/google/get-google-credentials index bb97e5f69..b463a2a4a 100755 --- a/zulip/integrations/google/get-google-credentials +++ b/zulip/integrations/google/get-google-credentials @@ -2,16 +2,35 @@ import argparse import os -from oauth2client import client, tools -from oauth2client.file import Storage +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow # type: ignore[import-untyped] + +parser = argparse.ArgumentParser(add_help=False) +parser.add_argument( + "--auth_host_name", default="localhost", help="Hostname when running a local web server." +) +parser.add_argument( + "--noauth_local_webserver", + action="store_true", + default=False, + help="Do not run a local web server.", +) +parser.add_argument( + "--auth_host_port", + default=[8080, 8090], + type=int, + nargs="*", + help="Port web server should listen on.", +) +flags = parser.parse_args() -flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() # If modifying these scopes, delete your previously saved credentials # at zulip/bots/gcal/ # NOTE: When adding more scopes, add them after the previous one in the same field, with a space # seperating them. -SCOPES = "https://www.googleapis.com/auth/calendar.readonly" +SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] # This file contains the information that google uses to figure out which application is requesting # this client's data. CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 @@ -19,7 +38,7 @@ APPLICATION_NAME = "Zulip Calendar Bot" HOME_DIR = os.path.expanduser("~") -def get_credentials() -> client.Credentials: +def get_credentials() -> Credentials: """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, @@ -28,19 +47,36 @@ def get_credentials() -> client.Credentials: Returns: Credentials, the obtained credential. """ - + credentials = None credential_path = os.path.join(HOME_DIR, "google-credentials.json") - - store = Storage(credential_path) - credentials = store.get() - if not credentials or credentials.invalid: - flow = client.flow_from_clientsecrets(os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES) - flow.user_agent = APPLICATION_NAME - # This attempts to open an authorization page in the default web browser, and asks the user - # to grant the bot access to their data. If the user grants permission, the run_flow() - # function returns new credentials. - credentials = tools.run_flow(flow, store, flags) + if os.path.exists(credential_path): + credentials = Credentials.from_authorized_user_file(credential_path, SCOPES) + if not credentials or not credentials.valid: + if credentials and credentials.expired and credentials.refresh_token: + credentials.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + os.path.join(HOME_DIR, CLIENT_SECRET_FILE), SCOPES + ) + if not flags.noauth_local_webserver: + credentials = flow.run_local_server( + host=flags.auth_host_name, port=flags.auth_host_port[0] + ) + # This attempts to open an authorization page in the default web browser, and asks the user + # to grant the bot access to their data. If the user grants permission, the run_flow() + # function returns new credentials. + else: + auth_url, _ = flow.authorization_url(prompt="consent") + print( + "Proceed to the following link in your browser:", + auth_url, + ) + auth_code = input("Enter the authorization code: ") + credentials = flow.fetch_token(code=auth_code) + with open(credential_path, "w") as token: + token.write(credentials.to_json()) print("Storing credentials to " + credential_path) + return credentials get_credentials() diff --git a/zulip/integrations/google/google-calendar b/zulip/integrations/google/google-calendar index 85906bd46..3d3228dd7 100755 --- a/zulip/integrations/google/google-calendar +++ b/zulip/integrations/google/google-calendar @@ -12,21 +12,18 @@ import time from typing import List, Optional, Set, Tuple import dateutil.parser -import httplib2 import pytz -from oauth2client import client -from oauth2client.file import Storage try: - from googleapiclient import discovery + from google.oauth2.credentials import Credentials + from googleapiclient.discovery import build except ImportError: - logging.exception("Install google-api-python-client") + logging.exception("Install google-api-python-client and google-auth-oauthlib") sys.exit(1) - sys.path.append(os.path.join(os.path.dirname(__file__), "../../")) import zulip -SCOPES = "https://www.googleapis.com/auth/calendar.readonly" +SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"] CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105 APPLICATION_NAME = "Zulip" HOME_DIR = os.path.expanduser("~") @@ -88,7 +85,7 @@ if not options.zulip_email: zulip_client = zulip.init_from_options(options) -def get_credentials() -> client.Credentials: +def get_credentials() -> Credentials: """Gets valid user credentials from storage. If nothing has been stored, or if the stored credentials are invalid, @@ -100,22 +97,20 @@ def get_credentials() -> client.Credentials: """ try: credential_path = os.path.join(HOME_DIR, "google-credentials.json") - - store = Storage(credential_path) - return store.get() - except client.Error: + credentials = Credentials.from_authorized_user_file(credential_path, SCOPES) + except ValueError: logging.exception("Error while trying to open the `google-credentials.json` file.") sys.exit(1) except OSError: logging.error("Run the get-google-credentials script from this directory first.") sys.exit(1) + else: + return credentials def populate_events() -> Optional[None]: - credentials = get_credentials() - creds = credentials.authorize(httplib2.Http()) - service = discovery.build("calendar", "v3", http=creds) - + creds = get_credentials() + service = build("calendar", "v3", credentials=creds) now = datetime.datetime.now(pytz.utc).isoformat() feed = ( service.events() diff --git a/zulip/integrations/google/requirements.txt b/zulip/integrations/google/requirements.txt index 139c0705b..018523c01 100644 --- a/zulip/integrations/google/requirements.txt +++ b/zulip/integrations/google/requirements.txt @@ -1,2 +1,3 @@ -httplib2>=0.22.0 -oauth2client>=4.1.3 +google-api-python-client>=2.157.0 +google-auth-httplib2>=0.2.0 +google-auth-oauthlib>=1.2.1