raphael
752c7c5577
- the icalHandler in the backend is used in two instances. First, when the user adds a new ical calendar into the database, routes.py passes the information (name and url) to the ical Handler. The calendars are saved in the database as type 'ical'. Then, when a connected device pulls current events, all the calendars which are of type 'ical' are pulled and parsed for current events. See the backend commit for details on what happens there. - All google Calendar related functions now have an additional check, to make sure that the calendar they are working on is of type 'Google'.
219 lines
8.1 KiB
Python
219 lines
8.1 KiB
Python
import google.oauth2.credentials
|
|
import google_auth_oauthlib.flow
|
|
import googleapiclient.discovery
|
|
from googleapiclient.discovery import build
|
|
|
|
from oauthlib.oauth2 import WebApplicationClient
|
|
import flask
|
|
|
|
# Python standard libraries
|
|
import json
|
|
import os
|
|
|
|
# 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,
|
|
)
|
|
import requests
|
|
|
|
from database.models import Calendar as dbCalendar
|
|
from backend import Calendar, Event
|
|
|
|
# Configuration class for the google client
|
|
# all necessary variables and secrets are defined in this
|
|
# aswell as some helper functions
|
|
class GoogleClient():
|
|
def __init__(self):
|
|
self.CLIENT_SECRETS_FILE = "certificate/client_secret.json"
|
|
|
|
with open("/home/calendarwatch/certificate/google_client.json", encoding='utf-8') as json_file:
|
|
self.google_client = json.load(json_file)
|
|
|
|
self.SCOPES = self.google_client.get('scopes')
|
|
self.API_SERVICE_NAME = 'calendar'
|
|
self.API_VERSION = 'v3'
|
|
|
|
# GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com"
|
|
self.GOOGLE_CLIENT_ID = self.google_client.get('client_id')
|
|
# GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ"
|
|
self.GOOGLE_CLIENT_SECRET = self.google_client.get('client_secret')
|
|
self.GOOGLE_DISCOVERY_URL = (
|
|
"https://accounts.google.com/.well-known/openid-configuration"
|
|
)
|
|
|
|
# OAuth 2 client setup
|
|
self.client = WebApplicationClient(self.GOOGLE_CLIENT_ID)
|
|
|
|
|
|
def build_credentials(self, token, refresh_token):
|
|
data = {}
|
|
data['token'] = token
|
|
data['refresh_token'] = refresh_token
|
|
data['token_uri'] = self.google_client.get('token_uri')
|
|
data['client_id'] = self.google_client.get('client_id')
|
|
data['client_secret'] = self.google_client.get('client_secret')
|
|
data['scopes'] = self.google_client.get('scopes')
|
|
return data
|
|
|
|
GC = GoogleClient()
|
|
|
|
# stuff for OAuth login
|
|
def login():
|
|
# Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
|
|
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
|
GC.CLIENT_SECRETS_FILE, scopes=GC.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 = "https://longitudecalendar.com/login/google/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
|
|
# Flask-Login helper to retrieve a user from our db
|
|
return authorization_url
|
|
|
|
def verifyResponse():
|
|
# 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(
|
|
GC.CLIENT_SECRETS_FILE, scopes=GC.SCOPES, state=state)
|
|
flow.redirect_uri = "https://longitudecalendar.com/login/google/callback"
|
|
|
|
# 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)
|
|
print(credentials_to_dict(credentials), flush=True)
|
|
session = flow.authorized_session()
|
|
return session, credentials_to_dict(credentials)
|
|
|
|
|
|
def get_google_provider_cfg():
|
|
return requests.get(GC.GOOGLE_DISCOVERY_URL).json()
|
|
|
|
def deleteAccount(user):
|
|
result = requests.post('https://oauth2.googleapis.com/revoke',
|
|
params={'token': user.google_token.token},
|
|
headers = {'content-type': 'applixation/x-www-form-urlencoded'})
|
|
print(result, flush=True)
|
|
return
|
|
|
|
|
|
def fetchCalendarEvents(user, calendars, startDate, endDate):
|
|
|
|
service = None
|
|
if user.google_token is not None:
|
|
client_token = GC.build_credentials(user.google_token.token,
|
|
user.google_token.refresh_token)
|
|
credentials = google.oauth2.credentials.Credentials(**client_token)
|
|
|
|
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
|
|
|
|
all_events = []
|
|
for calendar in calendars:
|
|
if (calendar.toggle == "True" and
|
|
calendar.calendar_type == "Google" and
|
|
service != None):
|
|
event_result = service.events().list(calendarId=calendar.calendar_id,
|
|
timeMin=startDate.isoformat(),
|
|
timeMax=endDate.isoformat(),
|
|
maxResults=10,
|
|
singleEvents=True,
|
|
orderBy='startTime').execute()
|
|
|
|
for event in event_result.get('items', []):
|
|
|
|
# create simple event
|
|
name = event.get('summary', '(no Title)')
|
|
start = event['start'].get('dateTime')
|
|
end = event['end'].get('dateTime')
|
|
newEvent = Event(name, start, end)
|
|
|
|
# handle weird colors from google
|
|
color = event.get('colorId')
|
|
if color == None:
|
|
newEvent.colorHex = calendar.color
|
|
newEvent.eventColorId = None
|
|
else:
|
|
newEvent.eventColorId = color
|
|
|
|
all_events.append(newEvent)
|
|
|
|
if service != None:
|
|
colors = service.colors().get().execute()
|
|
else:
|
|
colors = None
|
|
|
|
return all_events, colors
|
|
|
|
|
|
def fetchCalendars():
|
|
# get client api service
|
|
if current_user.google_token == None:
|
|
return [], None, None
|
|
client_token = GC.build_credentials(current_user.google_token.token,
|
|
current_user.google_token.refresh_token)
|
|
credentials = google.oauth2.credentials.Credentials(**client_token)
|
|
|
|
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
|
|
|
|
# get all calendars and put them into Calendar Class
|
|
page_token = None
|
|
calendars = []
|
|
while True:
|
|
calendar_list = service.calendarList().list(pageToken=page_token).execute()
|
|
for calendar in calendar_list['items']:
|
|
calendars.append(Calendar(name=calendar['summary'],
|
|
calType="Google",
|
|
calendarId=calendar['id'],
|
|
color=calendar['colorId']))
|
|
page_token = calendar_list.get('nextPageToken')
|
|
if not page_token:
|
|
break
|
|
|
|
|
|
colors = service.colors().get().execute()
|
|
|
|
return calendars, colors, credentials.token
|
|
|
|
|
|
|
|
def getUserCredentials(user):
|
|
credentials = GC.build_credentials(user.google_token.token,
|
|
user.google_token.refresh_token)
|
|
googleCreds = google.oauth2.credentials.Credentials(**credentials)
|
|
return googleCreds
|
|
|
|
|
|
|
|
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}
|
|
|
|
|