Refactored whoogle session mgmt
Now allows a fallback "default" session to be used if a user's browser is blocking cookiesmain
parent
64af72abb5
commit
32e837a5e0
|
@ -1,28 +1,28 @@
|
||||||
from app.utils.misc import generate_user_keys
|
from app.utils.misc import generate_user_keys
|
||||||
from cryptography.fernet import Fernet
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
import os
|
import os
|
||||||
|
|
||||||
app = Flask(__name__, static_folder=os.path.dirname(os.path.abspath(__file__)) + '/static')
|
app = Flask(__name__, static_folder=os.path.dirname(os.path.abspath(__file__)) + '/static')
|
||||||
app.user_elements = {}
|
app.user_elements = {}
|
||||||
app.config['SECRET_KEY'] = os.urandom(16)
|
app.default_key_set = generate_user_keys()
|
||||||
|
app.no_cookie_ips = []
|
||||||
|
app.config['SECRET_KEY'] = os.urandom(32)
|
||||||
app.config['SESSION_TYPE'] = 'filesystem'
|
app.config['SESSION_TYPE'] = 'filesystem'
|
||||||
app.config['VERSION_NUMBER'] = '0.2.0'
|
app.config['VERSION_NUMBER'] = '0.2.0'
|
||||||
app.config['APP_ROOT'] = os.getenv('APP_ROOT', os.path.dirname(os.path.abspath(__file__)))
|
app.config['APP_ROOT'] = os.getenv('APP_ROOT', os.path.dirname(os.path.abspath(__file__)))
|
||||||
app.config['STATIC_FOLDER'] = os.getenv('STATIC_FOLDER', os.path.join(app.config['APP_ROOT'], 'static'))
|
app.config['STATIC_FOLDER'] = os.getenv('STATIC_FOLDER', os.path.join(app.config['APP_ROOT'], 'static'))
|
||||||
app.config['CONFIG_PATH'] = os.getenv('CONFIG_VOLUME', app.config['STATIC_FOLDER'] + '/config')
|
app.config['CONFIG_PATH'] = os.getenv('CONFIG_VOLUME', os.path.join(app.config['STATIC_FOLDER'], 'config'))
|
||||||
app.config['USER_CONFIG'] = os.path.join(app.config['STATIC_FOLDER'], 'custom_config')
|
app.config['DEFAULT_CONFIG'] = os.path.join(app.config['CONFIG_PATH'], 'config.json')
|
||||||
app.config['SESSION_FILE_DIR'] = app.config['CONFIG_PATH']
|
app.config['SESSION_FILE_DIR'] = os.path.join(app.config['CONFIG_PATH'], 'session')
|
||||||
app.config['SESSION_COOKIE_SECURE'] = True
|
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
||||||
|
|
||||||
if not os.path.exists(app.config['CONFIG_PATH']):
|
if not os.path.exists(app.config['CONFIG_PATH']):
|
||||||
os.makedirs(app.config['CONFIG_PATH'])
|
os.makedirs(app.config['CONFIG_PATH'])
|
||||||
|
|
||||||
if not os.path.exists(app.config['USER_CONFIG']):
|
if not os.path.exists(app.config['SESSION_FILE_DIR']):
|
||||||
os.makedirs(app.config['USER_CONFIG'])
|
os.makedirs(app.config['SESSION_FILE_DIR'])
|
||||||
|
|
||||||
sess = Session()
|
Session(app)
|
||||||
sess.init_app(app)
|
|
||||||
|
|
||||||
from app import routes
|
from app import routes
|
||||||
|
|
|
@ -37,11 +37,19 @@ def auth_required(f):
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request_func():
|
def before_request_func():
|
||||||
# Generate secret key for user if unavailable
|
g.request_params = request.args if request.method == 'GET' else request.form
|
||||||
|
g.cookies_disabled = False
|
||||||
|
|
||||||
|
# Generate session values for user if unavailable
|
||||||
if not valid_user_session(session):
|
if not valid_user_session(session):
|
||||||
session['config'] = {'url': request.url_root}
|
session['config'] = json.load(open(app.config['DEFAULT_CONFIG'])) \
|
||||||
session['keys'] = generate_user_keys()
|
if os.path.exists(app.config['DEFAULT_CONFIG']) else {'url': request.url_root}
|
||||||
session['uuid'] = str(uuid.uuid4())
|
session['uuid'] = str(uuid.uuid4())
|
||||||
|
session['fernet_keys'] = generate_user_keys(True)
|
||||||
|
|
||||||
|
# Flag cookies as possibly disabled in order to prevent against
|
||||||
|
# unnecessary session directory expansion
|
||||||
|
g.cookies_disabled = True
|
||||||
|
|
||||||
if session['uuid'] not in app.user_elements:
|
if session['uuid'] not in app.user_elements:
|
||||||
app.user_elements.update({session['uuid']: 0})
|
app.user_elements.update({session['uuid']: 0})
|
||||||
|
@ -63,11 +71,22 @@ def before_request_func():
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def after_request_func(response):
|
def after_request_func(response):
|
||||||
# Regenerate element key if all elements have been served to user
|
|
||||||
if app.user_elements[session['uuid']] <= 0 and '/element' in request.url:
|
if app.user_elements[session['uuid']] <= 0 and '/element' in request.url:
|
||||||
session['keys']['element_key'] = Fernet.generate_key()
|
# Regenerate element key if all elements have been served to user
|
||||||
|
session['fernet_keys']['element_key'] = '' if not g.cookies_disabled else app.default_key_set['element_key']
|
||||||
app.user_elements[session['uuid']] = 0
|
app.user_elements[session['uuid']] = 0
|
||||||
|
|
||||||
|
# Check if address consistently has cookies blocked, in which case start removing session
|
||||||
|
# files after creation.
|
||||||
|
# Note: This is primarily done to prevent overpopulation of session directories, since browsers that
|
||||||
|
# block cookies will still trigger Flask's session creation routine with every request.
|
||||||
|
if g.cookies_disabled and request.remote_addr not in app.no_cookie_ips:
|
||||||
|
app.no_cookie_ips.append(request.remote_addr)
|
||||||
|
elif g.cookies_disabled and request.remote_addr in app.no_cookie_ips:
|
||||||
|
session_list = list(session.keys())
|
||||||
|
for key in session_list:
|
||||||
|
session.pop(key)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,6 +98,9 @@ def unknown_page(e):
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
@auth_required
|
@auth_required
|
||||||
def index():
|
def index():
|
||||||
|
# Reset keys
|
||||||
|
session['fernet_keys'] = generate_user_keys(g.cookies_disabled)
|
||||||
|
|
||||||
return render_template('index.html',
|
return render_template('index.html',
|
||||||
languages=Config.LANGUAGES,
|
languages=Config.LANGUAGES,
|
||||||
countries=Config.COUNTRIES,
|
countries=Config.COUNTRIES,
|
||||||
|
@ -103,8 +125,7 @@ def opensearch():
|
||||||
|
|
||||||
@app.route('/autocomplete', methods=['GET', 'POST'])
|
@app.route('/autocomplete', methods=['GET', 'POST'])
|
||||||
def autocomplete():
|
def autocomplete():
|
||||||
request_params = request.args if request.method == 'GET' else request.form
|
q = g.request_params.get('q')
|
||||||
q = request_params.get('q')
|
|
||||||
|
|
||||||
if not q and not request.data:
|
if not q and not request.data:
|
||||||
return jsonify({'?': []})
|
return jsonify({'?': []})
|
||||||
|
@ -117,11 +138,10 @@ def autocomplete():
|
||||||
@app.route('/search', methods=['GET', 'POST'])
|
@app.route('/search', methods=['GET', 'POST'])
|
||||||
@auth_required
|
@auth_required
|
||||||
def search():
|
def search():
|
||||||
# Clear previous elements and generate a new key each time a new search is performed
|
# Reset element counter
|
||||||
app.user_elements[session['uuid']] = 0
|
app.user_elements[session['uuid']] = 0
|
||||||
session['keys']['element_key'] = Fernet.generate_key()
|
|
||||||
|
|
||||||
search_util = RoutingUtils(request, g.user_config, session)
|
search_util = RoutingUtils(request, g.user_config, session, cookies_disabled=g.cookies_disabled)
|
||||||
query = search_util.new_search_query()
|
query = search_util.new_search_query()
|
||||||
|
|
||||||
# Redirect to home if invalid/blank search
|
# Redirect to home if invalid/blank search
|
||||||
|
@ -157,7 +177,7 @@ def config():
|
||||||
return json.dumps(g.user_config.__dict__)
|
return json.dumps(g.user_config.__dict__)
|
||||||
elif request.method == 'PUT':
|
elif request.method == 'PUT':
|
||||||
if 'name' in request.args:
|
if 'name' in request.args:
|
||||||
config_pkl = os.path.join(app.config['USER_CONFIG'], request.args.get('name'))
|
config_pkl = os.path.join(app.config['CONFIG_PATH'], request.args.get('name'))
|
||||||
session['config'] = pickle.load(open(config_pkl, 'rb')) if os.path.exists(config_pkl) else session['config']
|
session['config'] = pickle.load(open(config_pkl, 'rb')) if os.path.exists(config_pkl) else session['config']
|
||||||
return json.dumps(session['config'])
|
return json.dumps(session['config'])
|
||||||
else:
|
else:
|
||||||
|
@ -167,8 +187,13 @@ def config():
|
||||||
if 'url' not in config_data or not config_data['url']:
|
if 'url' not in config_data or not config_data['url']:
|
||||||
config_data['url'] = g.user_config.url
|
config_data['url'] = g.user_config.url
|
||||||
|
|
||||||
|
# Save config by name to allow a user to easily load later
|
||||||
if 'name' in request.args:
|
if 'name' in request.args:
|
||||||
pickle.dump(config_data, open(os.path.join(app.config['USER_CONFIG'], request.args.get('name')), 'wb'))
|
pickle.dump(config_data, open(os.path.join(app.config['CONFIG_PATH'], request.args.get('name')), 'wb'))
|
||||||
|
|
||||||
|
# Overwrite default config if user has cookies disabled
|
||||||
|
if g.cookies_disabled:
|
||||||
|
open(app.config['DEFAULT_CONFIG'], 'w').write(json.dumps(config_data, indent=4))
|
||||||
|
|
||||||
session['config'] = config_data
|
session['config'] = config_data
|
||||||
return redirect(config_data['url'])
|
return redirect(config_data['url'])
|
||||||
|
@ -196,7 +221,7 @@ def imgres():
|
||||||
@app.route('/element')
|
@app.route('/element')
|
||||||
@auth_required
|
@auth_required
|
||||||
def element():
|
def element():
|
||||||
cipher_suite = Fernet(session['keys']['element_key'])
|
cipher_suite = Fernet(session['fernet_keys']['element_key'])
|
||||||
src_url = cipher_suite.decrypt(request.args.get('url').encode()).decode()
|
src_url = cipher_suite.decrypt(request.args.get('url').encode()).decode()
|
||||||
src_type = request.args.get('type')
|
src_type = request.args.get('type')
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const handleUserInput = searchBar => {
|
||||||
xhrRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
xhrRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
xhrRequest.onload = function() {
|
xhrRequest.onload = function() {
|
||||||
if (xhrRequest.readyState === 4 && xhrRequest.status !== 200) {
|
if (xhrRequest.readyState === 4 && xhrRequest.status !== 200) {
|
||||||
alert("Error fetching autocomplete results");
|
// Do nothing if failed to fetch autocomplete results
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
|
from flask import current_app as app
|
||||||
|
|
||||||
SESSION_VALS = ['uuid', 'config', 'keys']
|
SESSION_VALS = ['uuid', 'config', 'fernet_keys']
|
||||||
|
|
||||||
|
|
||||||
def generate_user_keys():
|
def generate_user_keys(cookies_disabled=False) -> dict:
|
||||||
|
if cookies_disabled:
|
||||||
|
return app.default_key_set
|
||||||
|
|
||||||
# Generate/regenerate unique key per user
|
# Generate/regenerate unique key per user
|
||||||
return {
|
return {
|
||||||
'element_key': Fernet.generate_key(),
|
'element_key': Fernet.generate_key(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from app import app
|
|
||||||
from app.filter import Filter, get_first_link
|
from app.filter import Filter, get_first_link
|
||||||
|
from app.utils.misc import generate_user_keys
|
||||||
from app.request import gen_query
|
from app.request import gen_query
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from cryptography.fernet import Fernet, InvalidToken
|
from cryptography.fernet import Fernet, InvalidToken
|
||||||
|
@ -8,13 +8,14 @@ from typing import Any, Tuple
|
||||||
|
|
||||||
|
|
||||||
class RoutingUtils:
|
class RoutingUtils:
|
||||||
def __init__(self, request, config, session):
|
def __init__(self, request, config, session, cookies_disabled=False):
|
||||||
self.request_params = request.args if request.method == 'GET' else request.form
|
self.request_params = request.args if request.method == 'GET' else request.form
|
||||||
self.user_agent = request.headers.get('User-Agent')
|
self.user_agent = request.headers.get('User-Agent')
|
||||||
self.feeling_lucky = False
|
self.feeling_lucky = False
|
||||||
self.config = config
|
self.config = config
|
||||||
self.session = session
|
self.session = session
|
||||||
self.query = ''
|
self.query = ''
|
||||||
|
self.cookies_disabled = cookies_disabled
|
||||||
self.search_type = self.request_params.get('tbm') if 'tbm' in self.request_params else ''
|
self.search_type = self.request_params.get('tbm') if 'tbm' in self.request_params else ''
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
|
@ -30,8 +31,8 @@ class RoutingUtils:
|
||||||
return hasattr(self, name)
|
return hasattr(self, name)
|
||||||
|
|
||||||
def new_search_query(self) -> str:
|
def new_search_query(self) -> str:
|
||||||
app.user_elements[self.session['uuid']] = 0
|
# Generate a new element key each time a new search is performed
|
||||||
self.session['keys']['element_key'] = Fernet.generate_key()
|
self.session['fernet_keys']['element_key'] = generate_user_keys(cookies_disabled=self.cookies_disabled)['element_key']
|
||||||
|
|
||||||
q = self.request_params.get('q')
|
q = self.request_params.get('q')
|
||||||
|
|
||||||
|
@ -40,12 +41,12 @@ class RoutingUtils:
|
||||||
else:
|
else:
|
||||||
# Attempt to decrypt if this is an internal link
|
# Attempt to decrypt if this is an internal link
|
||||||
try:
|
try:
|
||||||
q = Fernet(self.session['keys']['text_key']).decrypt(q.encode()).decode()
|
q = Fernet(self.session['fernet_keys']['text_key']).decrypt(q.encode()).decode()
|
||||||
except InvalidToken:
|
except InvalidToken:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Reset text key
|
# Reset text key
|
||||||
self.session['keys']['text_key'] = Fernet.generate_key()
|
self.session['fernet_keys']['text_key'] = generate_user_keys(cookies_disabled=self.cookies_disabled)['text_key']
|
||||||
|
|
||||||
# Format depending on whether or not the query is a "feeling lucky" query
|
# Format depending on whether or not the query is a "feeling lucky" query
|
||||||
self.feeling_lucky = q.startswith('! ')
|
self.feeling_lucky = q.startswith('! ')
|
||||||
|
@ -55,7 +56,7 @@ class RoutingUtils:
|
||||||
def generate_response(self) -> Tuple[Any, int]:
|
def generate_response(self) -> Tuple[Any, int]:
|
||||||
mobile = 'Android' in self.user_agent or 'iPhone' in self.user_agent
|
mobile = 'Android' in self.user_agent or 'iPhone' in self.user_agent
|
||||||
|
|
||||||
content_filter = Filter(self.session['keys'], mobile=mobile, config=self.config)
|
content_filter = Filter(self.session['fernet_keys'], mobile=mobile, config=self.config)
|
||||||
full_query = gen_query(self.query, self.request_params, self.config, content_filter.near)
|
full_query = gen_query(self.query, self.request_params, self.config, content_filter.near)
|
||||||
get_body = g.user_request.send(query=full_query).text
|
get_body = g.user_request.send(query=full_query).text
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ def test_valid_session(client):
|
||||||
assert not valid_user_session(session)
|
assert not valid_user_session(session)
|
||||||
|
|
||||||
session['uuid'] = 'test'
|
session['uuid'] = 'test'
|
||||||
session['keys'] = generate_user_keys()
|
session['fernet_keys'] = generate_user_keys()
|
||||||
session['config'] = {}
|
session['config'] = {}
|
||||||
|
|
||||||
assert valid_user_session(session)
|
assert valid_user_session(session)
|
||||||
|
@ -26,11 +26,11 @@ def test_request_key_generation(client):
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
with client.session_transaction() as session:
|
||||||
assert valid_user_session(session)
|
assert valid_user_session(session)
|
||||||
text_key = session['keys']['text_key']
|
text_key = session['fernet_keys']['text_key']
|
||||||
|
|
||||||
rv = client.get('/search?q=test+2')
|
rv = client.get('/search?q=test+2')
|
||||||
assert rv._status_code == 200
|
assert rv._status_code == 200
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
with client.session_transaction() as session:
|
||||||
assert valid_user_session(session)
|
assert valid_user_session(session)
|
||||||
assert text_key not in session['keys']['text_key']
|
assert text_key not in session['fernet_keys']['text_key']
|
||||||
|
|
Loading…
Reference in New Issue