diff --git a/login/.app.py.swp b/login/.app.py.swp new file mode 100644 index 0000000..28cf93c Binary files /dev/null and b/login/.app.py.swp differ diff --git a/login/app.py b/login/app.py new file mode 100644 index 0000000..30f6928 --- /dev/null +++ b/login/app.py @@ -0,0 +1,274 @@ +# Python standard libraries +import json +import os +import sqlite3 + +# Third-party libraries +import flask +from flask import Flask, redirect, request, url_for +from flask_login import ( + LoginManager, + current_user, + login_required, + login_user, + logout_user, +) +from oauthlib.oauth2 import WebApplicationClient +import requests + +# Internal imports +from db import init_db_command +from user import User + + +import google.oauth2.credentials +import google_auth_oauthlib.flow +import googleapiclient.discovery + +# Configuration + +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/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"] +API_SERVICE_NAME = 'calendar' +API_VERSION = 'v3' + +GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com" +GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ" +GOOGLE_DISCOVERY_URL = ( + "https://accounts.google.com/.well-known/openid-configuration" +) + +API_SERVICE_NAME = 'calendar' +API_VERSION = 'v3' + +# Flask app setup +app = Flask(__name__) +app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24) + +# User session management setup +# https://flask-login.readthedocs.io/en/latest +login_manager = LoginManager() +login_manager.init_app(app) + +# Naive database setup +try: + init_db_command() +except sqlite3.OperationalError: + # Assume it's already been created + pass + +# OAuth 2 client setup +client = WebApplicationClient(GOOGLE_CLIENT_ID) + +# Flask-Login helper to retrieve a user from our db +@login_manager.user_loader +def load_user(user_id): + return User.get(user_id) + +@app.route("/") +def index(): + if current_user.is_authenticated: + return ( + "

Hello, {}! You're logged in! Email: {}

" + "

Google Profile Picture:

" + 'Google profile pic
' + 'Logout' + 'test API'.format( + current_user.name, current_user.email, current_user.profile_pic + ) + ) + else: + return 'Google Login' + +def get_google_provider_cfg(): + return requests.get(GOOGLE_DISCOVERY_URL).json() + +@app.route('/test') +def test_api_request(): + if 'credentials' not in flask.session: + return flask.redirect('login') + + # Load credentials from the session. + credentials = google.oauth2.credentials.Credentials( + **flask.session['credentials']) + + service = googleapiclient.discovery.build( + API_SERVICE_NAME, API_VERSION, credentials=credentials) + + + # 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("{}") + +@app.route("/login") +def login(): + + ''' + # Find out what URL to hit for Google login + google_provider_cfg = get_google_provider_cfg() + authorization_endpoint = google_provider_cfg["authorization_endpoint"] + + # Use library to construct the request for Google login and provide + # scopes that let you retrieve user's profile from Google + request_uri = client.prepare_request_uri( + authorization_endpoint, + redirect_uri=request.base_url + "/callback", + scope=["openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly"], + ) + return redirect(request_uri) + ''' + + # 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 = request.base_url + "/callback" + + 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 + + print("auth_url: " + authorization_url) + print("state: " + state) + + return flask.redirect(authorization_url) + +@app.route("/login/callback") +def callback(): + print("in callback") + + # 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 = request.base_url + + # 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) + + session = flow.authorized_session() + print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) + + # Create a user in your db with the information provided + # by Google + user = User( + id_=unique_id, name=users_name, email=users_email, profile_pic=picture + ) + + # Doesn't exist? Add it to the database. + if not User.get(unique_id): + User.create(unique_id, users_name, users_email, picture) + + # Begin user session by logging the user in + login_user(user) + return flask.redirect(flask.url_for('index')) + + ''' + # Get authorization code Google sent back to you + code = request.args.get("code") + # Find out what URL to hit to get tokens that allow you to ask for + # things on behalf of a user + google_provider_cfg = get_google_provider_cfg() + token_endpoint = google_provider_cfg["token_endpoint"] + + # Prepare and send a request to get tokens! Yay tokens! + token_url, headers, body = client.prepare_token_request( + token_endpoint, + authorization_response=request.url, + redirect_url=request.base_url, + code=code + ) + + print("asking for tokens") + + token_response = requests.post( + token_url, + headers=headers, + data=body, + auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), + ) + + # Parse the tokens! + client.parse_request_body_response(json.dumps(token_response.json())) + + # Now that you have tokens (yay) let's find and hit the URL + # from Google that gives you the user's profile information, + # including their Google profile image and email + userinfo_endpoint = google_provider_cfg["userinfo_endpoint"] + uri, headers, body = client.add_token(userinfo_endpoint) + userinfo_response = requests.get(uri, headers=headers, data=body) + print(userinfo_response.json()) + + # You want to make sure their email is verified. + # The user authenticated with Google, authorized your + # app, and now you've verified their email through Google! + if userinfo_response.json().get("email_verified"): + unique_id = userinfo_response.json()["sub"] + users_email = userinfo_response.json()["email"] + picture = userinfo_response.json()["picture"] + users_name = userinfo_response.json()["given_name"] + else: + return "User email not available or not verified by Google.", 400 + + # Create a user in your db with the information provided + # by Google + user = User( + id_=unique_id, name=users_name, email=users_email, profile_pic=picture + ) + + # Doesn't exist? Add it to the database. + if not User.get(unique_id): + User.create(unique_id, users_name, users_email, picture) + + # Begin user session by logging the user in + login_user(user) + + # Send user back to homepage + return redirect(url_for("index")) + + ''' + +@app.route("/logout") +@login_required +def logout(): + logout_user() + return redirect(url_for("index")) + +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} + +if __name__ == "__main__": + app.run('192.168.68.103.xip.io', 1234, ssl_context="adhoc", debug=True) + + + diff --git a/client_secret.json b/login/client_secret.json similarity index 100% rename from client_secret.json rename to login/client_secret.json diff --git a/login/db.py b/login/db.py new file mode 100644 index 0000000..60e4af5 --- /dev/null +++ b/login/db.py @@ -0,0 +1,38 @@ +# http://flask.pocoo.org/docs/1.0/tutorial/database/ +import sqlite3 + +import click +from flask import current_app, g +from flask.cli import with_appcontext + +def get_db(): + if "db" not in g: + g.db = sqlite3.connect( + "sqlite_db", detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + +def close_db(e=None): + db = g.pop("db", None) + + if db is not None: + db.close() + +def init_db(): + db = get_db() + + with current_app.open_resource("schema.sql") as f: + db.executescript(f.read().decode("utf8")) + +@click.command("init-db") +@with_appcontext +def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo("Initialized the database.") + +def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) diff --git a/login/schema.sql b/login/schema.sql new file mode 100644 index 0000000..5c71573 --- /dev/null +++ b/login/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE user ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + profile_pic TEXT NOT NULL +); diff --git a/login/sqlite_db b/login/sqlite_db new file mode 100644 index 0000000..42d1c94 Binary files /dev/null and b/login/sqlite_db differ diff --git a/login/user.py b/login/user.py new file mode 100644 index 0000000..e8165a5 --- /dev/null +++ b/login/user.py @@ -0,0 +1,34 @@ +from flask_login import UserMixin + +from db import get_db + +class User(UserMixin): + def __init__(self, id_, name, email, profile_pic): + self.id = id_ + self.name = name + self.email = email + self.profile_pic = profile_pic + + @staticmethod + def get(user_id): + db = get_db() + user = db.execute( + "SELECT * FROM user WHERE id = ?", (user_id,) + ).fetchone() + if not user: + return None + + user = User( + id_=user[0], name=user[1], email=user[2], profile_pic=user[3] + ) + return user + + @staticmethod + def create(id_, name, email, profile_pic): + db = get_db() + db.execute( + "INSERT INTO user (id, name, email, profile_pic) " + "VALUES (?, ?, ?, ?)", + (id_, name, email, profile_pic), + ) + db.commit() diff --git a/test1/client_secret.json b/test1/client_secret.json new file mode 100644 index 0000000..2104b71 --- /dev/null +++ b/test1/client_secret.json @@ -0,0 +1 @@ +{"web":{"client_id":"377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com","project_id":"calendarwatch-1584185874753","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"Hu_YWmKsVKUcLwyeINYzdKfZ","redirect_uris":["http://localhost:1234"],"javascript_origins":["http://raphael.maenle.net","https://raphael.maenle.net"]}} diff --git a/index.html b/test1/index.html similarity index 100% rename from index.html rename to test1/index.html diff --git a/index.js b/test1/index.js similarity index 100% rename from index.js rename to test1/index.js diff --git a/server.py b/test1/server.py similarity index 84% rename from server.py rename to test1/server.py index 75f47f4..e43f9ed 100644 --- a/server.py +++ b/test1/server.py @@ -14,17 +14,23 @@ 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' +SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"] +API_SERVICE_NAME = 'calendar' +API_VERSION = 'v3' 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.secret_key = 'N\x0cOZO\xca\x96b\xf0\\\xc7\x11\xbd(\x95\xe3' +class Calendar: + def __init__(self, summary_, calendarId_, color_): + self.summary = summary_ + self.calendarId = calendarId_ + self.color = color_ + @app.route('/') def index(): return print_index_table() @@ -39,17 +45,17 @@ def test_api_request(): credentials = google.oauth2.credentials.Credentials( **flask.session['credentials']) - drive = googleapiclient.discovery.build( + service = googleapiclient.discovery.build( API_SERVICE_NAME, API_VERSION, credentials=credentials) - files = drive.files().list().execute() + getCalendars(service) # 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) + return flask.jsonify("{}") @app.route('/authorize') @@ -74,6 +80,9 @@ def authorize(): # Store the state so the callback can verify the auth server response. flask.session['state'] = state + print("auth_url: " + authorization_url) + print("state: " + state) + return flask.redirect(authorization_url) @@ -96,6 +105,8 @@ def oauth2callback(): # credentials in a persistent database instead. credentials = flow.credentials flask.session['credentials'] = credentials_to_dict(credentials) + print("credentials: ") + print(credentials_to_dict(credentials)) return flask.redirect(flask.url_for('test_api_request')) @@ -157,6 +168,19 @@ def print_index_table(): ' API request again, you should go back to the auth flow.' + '') +def getCalendars(service): + page_token = None + calendars = [] + while True: + calendar_list = service.calendarList().list(pageToken=page_token).execute() + for calendar in calendar_list['items']: + calendars.append(Calendar(calendar['summary'], calendar['id'], calendar['colorId'])) + page_token = calendar_list.get('nextPageToken') + if not page_token: + break + + print(calendars) + if __name__ == '__main__': # When running locally, disable OAuthlib's HTTPs verification. diff --git a/website.py b/test1/website.py similarity index 100% rename from website.py rename to test1/website.py