From b00413de93ed170c3571ea64883a9a13db1c3366 Mon Sep 17 00:00:00 2001 From: raphael Date: Sat, 28 Mar 2020 10:57:18 +0000 Subject: [PATCH] added a server.py, which does google api handling via google auth oauthlib flow. works --- server.py | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++ website.py | 31 +++++++++- 2 files changed, 197 insertions(+), 3 deletions(-) create mode 100644 server.py diff --git a/server.py b/server.py new file mode 100644 index 0000000..75f47f4 --- /dev/null +++ b/server.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +import os +import flask +import requests + +import google.oauth2.credentials +import google_auth_oauthlib.flow +import googleapiclient.discovery + +# This variable specifies the name of a file that contains the OAuth 2.0 +# information for this application, including its client_id and client_secret. +CLIENT_SECRETS_FILE = "client_secret.json" + +# This OAuth 2.0 access scope allows for full read/write access to the +# authenticated user's account and requires requests to use an SSL connection. +SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] +API_SERVICE_NAME = 'drive' +API_VERSION = 'v2' + +app = flask.Flask(__name__) +# Note: A secret key is included in the sample so that it works. +# If you use this code in your application, replace this with a truly secret +# key. See https://flask.palletsprojects.com/quickstart/#sessions. +app.secret_key = 'REPLACE ME - this value is here as a placeholder.' + + +@app.route('/') +def index(): + return print_index_table() + + +@app.route('/test') +def test_api_request(): + if 'credentials' not in flask.session: + return flask.redirect('authorize') + + # Load credentials from the session. + credentials = google.oauth2.credentials.Credentials( + **flask.session['credentials']) + + drive = googleapiclient.discovery.build( + API_SERVICE_NAME, API_VERSION, credentials=credentials) + + files = drive.files().list().execute() + + # Save credentials back to session in case access token was refreshed. + # ACTION ITEM: In a production app, you likely want to save these + # credentials in a persistent database instead. + flask.session['credentials'] = credentials_to_dict(credentials) + + return flask.jsonify(**files) + + +@app.route('/authorize') +def authorize(): + # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps. + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES) + + # The URI created here must exactly match one of the authorized redirect URIs + # for the OAuth 2.0 client, which you configured in the API Console. If this + # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch' + # error. + flow.redirect_uri = flask.url_for('oauth2callback', _external=True) + + authorization_url, state = flow.authorization_url( + # Enable offline access so that you can refresh an access token without + # re-prompting the user for permission. Recommended for web server apps. + access_type='offline', + # Enable incremental authorization. Recommended as a best practice. + include_granted_scopes='true') + + # Store the state so the callback can verify the auth server response. + flask.session['state'] = state + + return flask.redirect(authorization_url) + + +@app.route('/oauth2callback') +def oauth2callback(): + # Specify the state when creating the flow in the callback so that it can + # verified in the authorization server response. + state = flask.session['state'] + + flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( + CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) + flow.redirect_uri = flask.url_for('oauth2callback', _external=True) + + # Use the authorization server's response to fetch the OAuth 2.0 tokens. + authorization_response = flask.request.url + flow.fetch_token(authorization_response=authorization_response) + + # Store credentials in the session. + # ACTION ITEM: In a production app, you likely want to save these + # credentials in a persistent database instead. + credentials = flow.credentials + flask.session['credentials'] = credentials_to_dict(credentials) + + return flask.redirect(flask.url_for('test_api_request')) + + +@app.route('/revoke') +def revoke(): + if 'credentials' not in flask.session: + return ('You need to authorize before ' + + 'testing the code to revoke credentials.') + + credentials = google.oauth2.credentials.Credentials( + **flask.session['credentials']) + + revoke = requests.post('https://oauth2.googleapis.com/revoke', + params={'token': credentials.token}, + headers = {'content-type': 'application/x-www-form-urlencoded'}) + + status_code = getattr(revoke, 'status_code') + if status_code == 200: + return('Credentials successfully revoked.' + print_index_table()) + else: + return('An error occurred.' + print_index_table()) + + +@app.route('/clear') +def clear_credentials(): + if 'credentials' in flask.session: + del flask.session['credentials'] + return ('Credentials have been cleared.

' + + print_index_table()) + + +def credentials_to_dict(credentials): + return {'token': credentials.token, + 'refresh_token': credentials.refresh_token, + 'token_uri': credentials.token_uri, + 'client_id': credentials.client_id, + 'client_secret': credentials.client_secret, + 'scopes': credentials.scopes} + +def print_index_table(): + return ('' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Test an API requestSubmit an API request and see a formatted JSON response. ' + + ' Go through the authorization flow if there are no stored ' + + ' credentials for the user.
Test the auth flow directlyGo directly to the authorization flow. If there are stored ' + + ' credentials, you still might not be prompted to reauthorize ' + + ' the application.
Revoke current credentialsRevoke the access token associated with the current user ' + + ' session. After revoking credentials, if you go to the test ' + + ' page, you should see an invalid_grant error.' + + '
Clear Flask session credentialsClear the access token currently stored in the user session. ' + + ' After clearing the token, if you test the ' + + ' API request again, you should go back to the auth flow.' + + '
') + + +if __name__ == '__main__': + # When running locally, disable OAuthlib's HTTPs verification. + # ACTION ITEM for developers: + # When running in production *do not* leave this option enabled. + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' + + # Specify a hostname and port that are set as a valid redirect URI + # for your API project in the Google API Console. + app.run('192.168.68.103.xip.io', 1234 , debug=True) \ No newline at end of file diff --git a/website.py b/website.py index b62478a..5cbf26c 100644 --- a/website.py +++ b/website.py @@ -1,11 +1,21 @@ from google.oauth2 import id_token from google.auth.transport import requests +import pickle +import os.path +from googleapiclient.discovery import build + from http.server import HTTPServer, SimpleHTTPRequestHandler, BaseHTTPRequestHandler import socketserver import logging import json +# some_file.py +import sys +# insert at 1, 0 is the script path (or '' in REPL) +sys.path.insert(1, '../calenderwatch_server/') + + Handler = SimpleHTTPRequestHandler class S(BaseHTTPRequestHandler): @@ -27,7 +37,8 @@ class S(BaseHTTPRequestHandler): print("in post method") self.data_string = self.rfile.read(int(self.headers['Content-Length'])) print('checking client id') - checkClientId(self.data_string) + if checkClientId(self.data_string): + getApiAuth(self.data_string) self.send_response(200) self.end_headers() self.wfile.write("Hello".encode('utf-8')) @@ -66,10 +77,24 @@ def checkClientId(token): # ID token is valid. Get the user's Google Account ID from the decoded token. userid = idinfo['sub'] print(f"valid user id: {userid}") + return True except ValueError: # ID token is invalid print('invalid token') - pass + return False + + +def getApiAuth(token): + + with open('client_secret.json', 'r') as json_file: + clientSecret = json.load(json_file) + CLIENT_ID = clientSecret["web"]["client_id"] + # Specify the CLIENT_ID of the app that accesses the backend: + idinfo = id_token.verify_oauth2_token(token, requests.Request(), CLIENT_ID) + + # creds = pickle.load(idinfo) + + service = build('calendar', 'v3', credentials=idinfo) if __name__ == '__main__': from sys import argv @@ -77,4 +102,4 @@ if __name__ == '__main__': if len(argv) == 2: run(port=int(argv[1])) else: - run() \ No newline at end of file + run()