Set logging process
* Sets logging method and replaces basic "print" with logs * Replaces /HOME/USER/.config/ directory with /HOME/USER/.inopy directory * Moves config directory in new /HOME/USER/.inopy directory and creates a new "logs" directory * Adapts README filemaster
parent
f4fe6e717e
commit
7c3c922529
|
@ -11,7 +11,7 @@ Inopy is a Python application that retrieves unread articles from the [Inoreader
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
It uses OAuth authentication for accessing the [API](https://www.inoreader.com/developers) and stores the authentication data in a JSON configuration file located at `/HOME/USER/.config/inopy/config.json`. The program is divided into multiple modules and functions for making API requests, parsing response data, refreshing tokens, and sending notifications.
|
It uses OAuth authentication for accessing the [API](https://www.inoreader.com/developers) and stores the authentication data in a JSON configuration file located at `/HOME/USER/.inopy/config/config.json`. The program is divided into multiple modules and functions for making API requests, parsing response data, refreshing tokens, sending notifications and logging the processes.
|
||||||
|
|
||||||
The code is organized into the following modules:
|
The code is organized into the following modules:
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ The code is organized into the following modules:
|
||||||
- `oauth.py`: Implements the OAuth authentication flow using Flask.
|
- `oauth.py`: Implements the OAuth authentication flow using Flask.
|
||||||
- `refresh.py`: Contains a `refresh` function for refreshing OAuth access and refresh tokens and updating the configuration file.
|
- `refresh.py`: Contains a `refresh` function for refreshing OAuth access and refresh tokens and updating the configuration file.
|
||||||
- `notif.py`: Provides a function for sending notifications using D-Bus (only tested with Cinnamon desktop environment).
|
- `notif.py`: Provides a function for sending notifications using D-Bus (only tested with Cinnamon desktop environment).
|
||||||
|
- `logs.py`: Defines logging options.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ pip install requests pydbus flask waitress
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The first time `ino.py` module is run, it will check if `config.json` exists in `/HOME/USER/.config/inopy/`. If not it will prompt user for configuration details and create the `config.json` file. The file should contain the OAuth endpoint, client ID, client secret, callback URL, scope, CSRF value and home URL.
|
The first time `ino.py` module is run, it will check if `config.json` exists in `/HOME/USER/.inopy/config/`. If not it will prompt user for configuration details and create the `config.json` file. The file should contain the OAuth endpoint, client ID, client secret, callback URL, scope, CSRF value and home URL.
|
||||||
|
|
||||||
It should also contain the Inoreader API endpoints, notification labels, production status, browser path, host and port.
|
It should also contain the Inoreader API endpoints, notification labels, production status, browser path, host and port.
|
||||||
|
|
||||||
|
|
15
config.py
15
config.py
|
@ -32,6 +32,11 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
from logs import LogFile
|
||||||
|
|
||||||
|
# Set logs file
|
||||||
|
log_file = LogFile()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
================================================================
|
================================================================
|
||||||
|
@ -51,8 +56,10 @@ import os
|
||||||
def get_config(config_path, config_file_path):
|
def get_config(config_path, config_file_path):
|
||||||
|
|
||||||
if os.path.exists(config_path):
|
if os.path.exists(config_path):
|
||||||
|
logging.info(f'Found config directory at {config_path}')
|
||||||
|
|
||||||
if os.path.exists(config_file_path):
|
if os.path.exists(config_file_path):
|
||||||
|
logging.info(f'Found config file at {config_file_path}!')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -64,7 +71,8 @@ def get_config(config_path, config_file_path):
|
||||||
|
|
||||||
# If the config directory doesn't exist, create it
|
# If the config directory doesn't exist, create it
|
||||||
os.mkdir(config_path)
|
os.mkdir(config_path)
|
||||||
print(f'{config_path} created!')
|
logging.info(f'{config_path} created!')
|
||||||
|
|
||||||
create_file(config_file_path)
|
create_file(config_file_path)
|
||||||
|
|
||||||
# load config content
|
# load config content
|
||||||
|
@ -167,7 +175,8 @@ def create_file(config_file_path):
|
||||||
with open(config_file_path, "w") as file:
|
with open(config_file_path, "w") as file:
|
||||||
json.dump(config, file, indent=4)
|
json.dump(config, file, indent=4)
|
||||||
|
|
||||||
print(f"{config_file_path} created successfully!")
|
#print(f"{config_file_path} created successfully!")
|
||||||
|
logging.info(f'Created config file at {config_file_path}!')
|
||||||
|
|
||||||
'''
|
'''
|
||||||
==============================================
|
==============================================
|
||||||
|
@ -182,7 +191,7 @@ def config():
|
||||||
|
|
||||||
# Set the path of config file to
|
# Set the path of config file to
|
||||||
# /HOME/USER/.config/inopy/config.json
|
# /HOME/USER/.config/inopy/config.json
|
||||||
config_path = os.path.join(os.environ['HOME'], '.config/inopy')
|
config_path = os.path.join(os.environ['HOME'], '.inopy/config')
|
||||||
config_file = 'config.json'
|
config_file = 'config.json'
|
||||||
config_file_path = os.path.join(config_path, config_file)
|
config_file_path = os.path.join(config_path, config_file)
|
||||||
|
|
||||||
|
|
81
ino.py
81
ino.py
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
It performs token refreshing process if the API request returns an unauthorized status code.
|
It performs token refreshing process if the API request returns an unauthorized status code.
|
||||||
|
|
||||||
Inopy is structured into functions and modules for making API requests, parsing response data, refreshing tokens and sending notifications.
|
Inopy is structured into functions and modules for making API requests, parsing response data, refreshing tokens, sending notifications and logging the processes.
|
||||||
|
|
||||||
For more information about OAuth authentication, plase see <https://www.inoreader.com/developers/oauth>
|
For more information about OAuth authentication, plase see <https://www.inoreader.com/developers/oauth>
|
||||||
|
|
||||||
|
@ -35,9 +35,14 @@
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import notif
|
import notif
|
||||||
|
import logging
|
||||||
from config import config
|
from config import config
|
||||||
from oauth import app, run_app
|
from oauth import run_app
|
||||||
from refresh import refresh
|
from refresh import refresh
|
||||||
|
from logs import LogFile
|
||||||
|
|
||||||
|
# Set logs file
|
||||||
|
log_file = LogFile()
|
||||||
|
|
||||||
'''
|
'''
|
||||||
===========================================
|
===========================================
|
||||||
|
@ -100,7 +105,12 @@ subscriptions = {}
|
||||||
categories = []
|
categories = []
|
||||||
|
|
||||||
# Make API request to get unread counts
|
# Make API request to get unread counts
|
||||||
unread_response = APIrequest(unread_counts_url, bearer)
|
|
||||||
|
try:
|
||||||
|
unread_response = APIrequest(unread_counts_url, bearer)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
===================================================
|
===================================================
|
||||||
|
@ -138,34 +148,47 @@ unread_response = APIrequest(unread_counts_url, bearer)
|
||||||
============================================
|
============================================
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Check for 403 error case
|
try:
|
||||||
if unread_response.status_code == 403:
|
|
||||||
run_app()
|
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
# Check for 403 error case
|
||||||
new_config = json.load(config_file)
|
if unread_response.status_code == 403:
|
||||||
|
logging.info('Token not available: starting oauth process...')
|
||||||
|
run_app()
|
||||||
|
|
||||||
bearer = new_config['oauth']['bearer']
|
with open(config_path) as config_file:
|
||||||
|
new_config = json.load(config_file)
|
||||||
|
|
||||||
unread_response = APIrequest(unread_counts_url, bearer)
|
bearer = new_config['oauth']['bearer']
|
||||||
|
|
||||||
# Check for 401 error case
|
unread_response = APIrequest(unread_counts_url, bearer)
|
||||||
elif unread_response.status_code == 401:
|
|
||||||
refresh(config_path, endpoint, client_id, client_secret, refresh_token)
|
|
||||||
|
|
||||||
with open(config_path) as config_file:
|
# Check for 401 error case
|
||||||
new_config = json.load(config_file)
|
elif unread_response.status_code == 401:
|
||||||
|
logging.info('Token expired: starting refresh process...')
|
||||||
|
refresh(config_path, endpoint, client_id, client_secret, refresh_token)
|
||||||
|
|
||||||
bearer = new_config['oauth']['bearer']
|
with open(config_path) as config_file:
|
||||||
|
new_config = json.load(config_file)
|
||||||
unread_response = APIrequest(unread_counts_url, bearer)
|
|
||||||
|
|
||||||
# Proceed with the code execution
|
bearer = new_config['oauth']['bearer']
|
||||||
elif unread_response.status_code == 200:
|
|
||||||
pass
|
unread_response = APIrequest(unread_counts_url, bearer)
|
||||||
|
|
||||||
|
# Proceed with the code execution
|
||||||
|
elif unread_response.status_code == 200:
|
||||||
|
logging.info('API request ok: retrieving data...')
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
# Make API request to get feeds list
|
# Make API request to get feeds list
|
||||||
feeds_list_response = APIrequest(feeds_list_url, bearer)
|
|
||||||
|
try:
|
||||||
|
feeds_list_response = APIrequest(feeds_list_url, bearer)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
=======================================
|
=======================================
|
||||||
|
@ -255,8 +278,14 @@ for unread_id, count in unreadcounts.items():
|
||||||
====================================
|
====================================
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if message != "":
|
try:
|
||||||
notif.send_notification(summary, message)
|
if message != "":
|
||||||
|
notif.send_notification(summary, message)
|
||||||
|
logging.info('Notification successfully sent!')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
logging.info('No unread articles. Notification not sent.')
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
|
@ -0,0 +1,41 @@
|
||||||
|
"""
|
||||||
|
=========================================================================================
|
||||||
|
|
||||||
|
Copyright © 2023 Alexandre Racine <https://alex-racine.ch>
|
||||||
|
|
||||||
|
This file is part of Inopy.
|
||||||
|
|
||||||
|
Inopy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
Inopy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with Inopy. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
=========================================================================================
|
||||||
|
|
||||||
|
DISCLAIMER: parts of this code and comments blocks were created
|
||||||
|
with the help of ChatGPT developped by OpenAI <https://openai.com/>
|
||||||
|
Followed by human reviewing, refactoring and fine-tuning.
|
||||||
|
|
||||||
|
=========================================================================================
|
||||||
|
|
||||||
|
This module aims to log the program processes.
|
||||||
|
|
||||||
|
=========================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Setting the logs file
|
||||||
|
def LogFile():
|
||||||
|
|
||||||
|
# Determine the user's home directory
|
||||||
|
logs_dir = os.path.join(os.environ['HOME'], '.inopy/logs')
|
||||||
|
os.makedirs(logs_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
log_file = os.path.join(logs_dir, "inopy.log")
|
||||||
|
|
||||||
|
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
datefmt='%m/%d/%Y %I:%M:%S %p', filename=log_file, level=logging.DEBUG)
|
456
oauth.py
456
oauth.py
|
@ -34,225 +34,255 @@ import time
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
import logging
|
||||||
from flask import Flask, request, redirect, render_template
|
from flask import Flask, request, redirect, render_template
|
||||||
from config import config
|
from config import config
|
||||||
from waitress import serve
|
from waitress import serve
|
||||||
|
from logs import LogFile
|
||||||
|
|
||||||
'''
|
# Set logs file
|
||||||
===================================
|
log_file = LogFile()
|
||||||
Load configuration values
|
|
||||||
|
|
||||||
Extract values from the config
|
|
||||||
dictionary
|
|
||||||
|
|
||||||
Build the URL for authorization
|
|
||||||
===================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
config = config()
|
|
||||||
|
|
||||||
endpoint = config['endpoint']
|
|
||||||
client_id = config['client_id']
|
|
||||||
client_secret = config['client_secret']
|
|
||||||
callback = config['callback']
|
|
||||||
scope = config['scope']
|
|
||||||
CSRF = config['csrf']
|
|
||||||
home_url = config['home_url']
|
|
||||||
prod_status = config['prod_status']
|
|
||||||
browser_path = config['browser_path']
|
|
||||||
host = config['host']
|
|
||||||
port = config['port']
|
|
||||||
config_file_path = config['config_file_path']
|
|
||||||
|
|
||||||
url = 'https://www.inoreader.com/oauth2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}'.format(client_id, callback, scope, CSRF)
|
|
||||||
|
|
||||||
'''
|
|
||||||
==========================
|
|
||||||
Initiate the Flask app
|
|
||||||
==========================
|
|
||||||
'''
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
'''
|
|
||||||
==================================
|
|
||||||
When the home url is accessed:
|
|
||||||
|
|
||||||
* Redirect the user to the
|
|
||||||
authorization URL
|
|
||||||
==================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return redirect(url)
|
|
||||||
|
|
||||||
'''
|
|
||||||
=====================================================
|
|
||||||
When the oauth-callback url is accessed:
|
|
||||||
|
|
||||||
* Get the authorization code and other
|
|
||||||
parameters from the callback URL
|
|
||||||
|
|
||||||
* Check CSRF validation token and if there
|
|
||||||
is an error parameter in the URL
|
|
||||||
|
|
||||||
* Request bearer token and refresh token
|
|
||||||
|
|
||||||
* Save the bearer token and refresh token
|
|
||||||
to the config file
|
|
||||||
|
|
||||||
* If process is successfull:
|
|
||||||
|
|
||||||
* render the success template with the
|
|
||||||
response
|
|
||||||
|
|
||||||
* If CSRF failed:
|
|
||||||
|
|
||||||
* render the CSRF failure template
|
|
||||||
|
|
||||||
* If an error parameter is present in the URL:
|
|
||||||
|
|
||||||
* Render the OAuth error template
|
|
||||||
=====================================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
@app.route('/oauth-callback')
|
|
||||||
|
|
||||||
def oauth_callback():
|
|
||||||
authorization_code = request.args.get('code')
|
|
||||||
csrf_check = request.args.get('state')
|
|
||||||
error_param = request.args.get('error')
|
|
||||||
|
|
||||||
csrf = True if csrf_check == CSRF else False
|
|
||||||
error = True if error_param is not None else False
|
|
||||||
|
|
||||||
if csrf and not error:
|
|
||||||
access_token_url = endpoint
|
|
||||||
|
|
||||||
# Prepare data to request bearer token
|
|
||||||
payload = {
|
|
||||||
'grant_type': 'authorization_code',
|
|
||||||
'code': authorization_code,
|
|
||||||
'client_id': client_id,
|
|
||||||
'client_secret': client_secret,
|
|
||||||
'redirect_uri': callback
|
|
||||||
}
|
|
||||||
|
|
||||||
# Request bearer token and refresh token
|
|
||||||
response = requests.post(access_token_url, data=payload)
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
|
|
||||||
access_token = response.json()['access_token']
|
|
||||||
refresh_token = response.json()['refresh_token']
|
|
||||||
|
|
||||||
with open(config_file_path, 'r+') as config_file:
|
|
||||||
config = json.load(config_file)
|
|
||||||
|
|
||||||
# Save the bearer token and refresh token to the config file
|
|
||||||
config['oauth']['bearer'] = access_token
|
|
||||||
config['oauth']['refresh_token'] = refresh_token
|
|
||||||
|
|
||||||
config_file.seek(0)
|
|
||||||
|
|
||||||
json.dump(config, config_file, indent=4)
|
|
||||||
config_file.truncate()
|
|
||||||
|
|
||||||
return render_template('success.html', response=(access_token, refresh_token))
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not csrf:
|
|
||||||
return render_template('csrf-failed.html', response=(CSRF, csrf_check))
|
|
||||||
|
|
||||||
elif error:
|
|
||||||
error_content = request.args.get('error_description')
|
|
||||||
return render_template('oauth-error.html', response=(error_param, error_content))
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
'''
|
|
||||||
======================================
|
|
||||||
When the shutdown url is accessed:
|
|
||||||
|
|
||||||
* Shut down the Flask server
|
|
||||||
gracefully
|
|
||||||
======================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
@app.route('/shutdown')
|
|
||||||
def shutdown():
|
|
||||||
request.environ.get('werkzeug.server.shutdown')
|
|
||||||
return 'Close this browser to terminate the process!'
|
|
||||||
|
|
||||||
'''
|
|
||||||
======================================
|
|
||||||
Define a function to start the
|
|
||||||
Flask server using Waitress in
|
|
||||||
production mode
|
|
||||||
======================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
def start_server():
|
|
||||||
serve(app, host=host, port=port)
|
|
||||||
|
|
||||||
'''
|
|
||||||
======================================
|
|
||||||
Define a function to start the
|
|
||||||
production server inside a new
|
|
||||||
thread.
|
|
||||||
|
|
||||||
This is to ensure the server is
|
|
||||||
actually already running before
|
|
||||||
opening the web browser
|
|
||||||
======================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
def run_prod():
|
|
||||||
server_thread = threading.Thread(target=start_server)
|
|
||||||
server_thread.start()
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Launch a separate browser process with a new profile
|
|
||||||
subprocess.run([browser_path, "-CreateProfile", "new_profile", "-no-remote"])
|
|
||||||
subprocess.run([browser_path, "-P", "new_profile", "-no-remote", home_url])
|
|
||||||
|
|
||||||
'''
|
|
||||||
======================================
|
|
||||||
Define a function to start the
|
|
||||||
development server inside a new
|
|
||||||
thread.
|
|
||||||
|
|
||||||
This is to ensure the server is
|
|
||||||
actually already running before
|
|
||||||
opening the web browser
|
|
||||||
======================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
def run_dev():
|
|
||||||
server_thread = threading.Thread(target=start_server)
|
|
||||||
server_thread.start()
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Open the home URL in the default web browser
|
|
||||||
webbrowser.open(home_url)
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
'''
|
|
||||||
======================================
|
|
||||||
Determine whether to run the
|
|
||||||
production or development server
|
|
||||||
based on the config
|
|
||||||
======================================
|
|
||||||
'''
|
|
||||||
|
|
||||||
def run_app():
|
def run_app():
|
||||||
|
|
||||||
if prod_status == "true":
|
'''
|
||||||
run_prod()
|
===================================
|
||||||
|
Load configuration values
|
||||||
else:
|
|
||||||
run_dev()
|
Extract values from the config
|
||||||
|
dictionary
|
||||||
|
|
||||||
|
Build the URL for authorization
|
||||||
|
===================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
conf = config()
|
||||||
|
|
||||||
|
endpoint = conf['endpoint']
|
||||||
|
client_id = conf['client_id']
|
||||||
|
client_secret = conf['client_secret']
|
||||||
|
callback = conf['callback']
|
||||||
|
scope = conf['scope']
|
||||||
|
CSRF = conf['csrf']
|
||||||
|
home_url = conf['home_url']
|
||||||
|
prod_status = conf['prod_status']
|
||||||
|
browser_path = conf['browser_path']
|
||||||
|
host = conf['host']
|
||||||
|
port = conf['port']
|
||||||
|
config_file_path = conf['config_file_path']
|
||||||
|
|
||||||
|
url = 'https://www.inoreader.com/oauth2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}'.format(client_id, callback, scope, CSRF)
|
||||||
|
|
||||||
|
'''
|
||||||
|
==========================
|
||||||
|
Initiate the Flask app
|
||||||
|
==========================
|
||||||
|
'''
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
'''
|
||||||
|
==================================
|
||||||
|
When the home url is accessed:
|
||||||
|
|
||||||
|
* Redirect the user to the
|
||||||
|
authorization URL
|
||||||
|
==================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return redirect(url)
|
||||||
|
|
||||||
|
logging.info('Redirecting to authorization URL...')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
|
'''
|
||||||
|
=====================================================
|
||||||
|
When the oauth-callback url is accessed:
|
||||||
|
|
||||||
|
* Get the authorization code and other
|
||||||
|
parameters from the callback URL
|
||||||
|
|
||||||
|
* Check CSRF validation token and if there
|
||||||
|
is an error parameter in the URL
|
||||||
|
|
||||||
|
* Request bearer token and refresh token
|
||||||
|
|
||||||
|
* Save the bearer token and refresh token
|
||||||
|
to the config file
|
||||||
|
|
||||||
|
* If process is successfull:
|
||||||
|
|
||||||
|
* render the success template with the
|
||||||
|
response
|
||||||
|
|
||||||
|
* If CSRF failed:
|
||||||
|
|
||||||
|
* render the CSRF failure template
|
||||||
|
|
||||||
|
* If an error parameter is present in the URL:
|
||||||
|
|
||||||
|
* Render the OAuth error template
|
||||||
|
=====================================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
@app.route('/oauth-callback')
|
||||||
|
|
||||||
|
def oauth_callback():
|
||||||
|
authorization_code = request.args.get('code')
|
||||||
|
csrf_check = request.args.get('state')
|
||||||
|
error_param = request.args.get('error')
|
||||||
|
|
||||||
|
csrf = True if csrf_check == CSRF else False
|
||||||
|
error = True if error_param is not None else False
|
||||||
|
|
||||||
|
if csrf and not error:
|
||||||
|
access_token_url = endpoint
|
||||||
|
|
||||||
|
# Prepare data to request bearer token
|
||||||
|
payload = {
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'code': authorization_code,
|
||||||
|
'client_id': client_id,
|
||||||
|
'client_secret': client_secret,
|
||||||
|
'redirect_uri': callback
|
||||||
|
}
|
||||||
|
|
||||||
|
# Request bearer token and refresh token
|
||||||
|
response = requests.post(access_token_url, data=payload)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
|
||||||
|
access_token = response.json()['access_token']
|
||||||
|
refresh_token = response.json()['refresh_token']
|
||||||
|
|
||||||
|
with open(config_file_path, 'r+') as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
|
||||||
|
# Save the bearer token and refresh token to the config file
|
||||||
|
config['oauth']['bearer'] = access_token
|
||||||
|
config['oauth']['refresh_token'] = refresh_token
|
||||||
|
|
||||||
|
config_file.seek(0)
|
||||||
|
|
||||||
|
json.dump(config, config_file, indent=4)
|
||||||
|
config_file.truncate()
|
||||||
|
|
||||||
|
logging.info('New token obtained successfully...')
|
||||||
|
return render_template('success.html', response=(access_token, refresh_token))
|
||||||
|
|
||||||
|
else:
|
||||||
|
if not csrf:
|
||||||
|
logging.warning('CRSF validation failed...')
|
||||||
|
return render_template('csrf-failed.html', response=(CSRF, csrf_check))
|
||||||
|
|
||||||
|
elif error:
|
||||||
|
error_content = request.args.get('error_description')
|
||||||
|
logging.debug(error_content)
|
||||||
|
return render_template('oauth-error.html', response=(error_param, error_content))
|
||||||
|
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
|
'''
|
||||||
|
======================================
|
||||||
|
When the shutdown url is accessed:
|
||||||
|
|
||||||
|
* Shut down the Flask server
|
||||||
|
gracefully
|
||||||
|
======================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
@app.route('/shutdown')
|
||||||
|
def shutdown():
|
||||||
|
request.environ.get('werkzeug.server.shutdown')
|
||||||
|
logging.info('Shutting down Flask server...')
|
||||||
|
return 'Close this browser to terminate the process!'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
|
'''
|
||||||
|
======================================
|
||||||
|
Define a function to start the
|
||||||
|
Flask server using Waitress in
|
||||||
|
production mode
|
||||||
|
======================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
def start_server():
|
||||||
|
serve(app, host=host, port=port)
|
||||||
|
|
||||||
|
'''
|
||||||
|
======================================
|
||||||
|
Define a function to start the
|
||||||
|
production server inside a new
|
||||||
|
thread.
|
||||||
|
|
||||||
|
This is to ensure the server is
|
||||||
|
actually already running before
|
||||||
|
opening the web browser
|
||||||
|
======================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
def run_prod():
|
||||||
|
logging.info('Running program in production mode...')
|
||||||
|
server_thread = threading.Thread(target=start_server)
|
||||||
|
server_thread.start()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Launch a separate browser process with a new profile
|
||||||
|
subprocess.run([browser_path, "-CreateProfile", "new_profile", "-no-remote"])
|
||||||
|
subprocess.run([browser_path, "-P", "new_profile", "-no-remote", home_url])
|
||||||
|
|
||||||
|
'''
|
||||||
|
======================================
|
||||||
|
Define a function to start the
|
||||||
|
development server inside a new
|
||||||
|
thread.
|
||||||
|
|
||||||
|
This is to ensure the server is
|
||||||
|
actually already running before
|
||||||
|
opening the web browser
|
||||||
|
======================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
def run_dev():
|
||||||
|
logging.info('Running program in development mode...')
|
||||||
|
server_thread = threading.Thread(target=start_server)
|
||||||
|
server_thread.start()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Open the home URL in the default web browser
|
||||||
|
webbrowser.open(home_url)
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
'''
|
||||||
|
======================================
|
||||||
|
Determine whether to run the
|
||||||
|
production or development server
|
||||||
|
based on the config
|
||||||
|
======================================
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
if prod_status == "true":
|
||||||
|
run_prod()
|
||||||
|
|
||||||
|
else:
|
||||||
|
run_dev()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(e)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
======================================
|
======================================
|
||||||
|
|
|
@ -58,6 +58,11 @@
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
from logs import LogFile
|
||||||
|
|
||||||
|
# Set logs file
|
||||||
|
log_file = LogFile()
|
||||||
|
|
||||||
def refresh(config_path, endpoint, client_id, client_secret, refresh_token):
|
def refresh(config_path, endpoint, client_id, client_secret, refresh_token):
|
||||||
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||||
|
@ -72,10 +77,10 @@ def refresh(config_path, endpoint, client_id, client_secret, refresh_token):
|
||||||
response = requests.post(endpoint, data=payload, headers=headers)
|
response = requests.post(endpoint, data=payload, headers=headers)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
print("Request was successful.")
|
logging.info("Request was successful. Token was refreshed...")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Request failed with status code:", response.status_code)
|
logging.debug(f'Request failed with status code: {response.status_code}')
|
||||||
|
|
||||||
data = json.loads(response.text)
|
data = json.loads(response.text)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue