-
Notifications
You must be signed in to change notification settings - Fork 691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor scrypt hashing and source app session mgmt pt 2/3 #5694
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,6 @@ pyotp | |
qrcode | ||
redis>=3.3.6 | ||
rq>=1.1.0 | ||
scrypt | ||
setuptools>=56.0.0 | ||
sh | ||
SQLAlchemy>=1.3.0 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,13 +4,12 @@ | |
|
||
import werkzeug | ||
from flask import (Flask, render_template, escape, flash, Markup, request, g, session, | ||
url_for, redirect) | ||
url_for) | ||
from flask_babel import gettext | ||
from flask_assets import Environment | ||
from flask_wtf.csrf import CSRFProtect, CSRFError | ||
from jinja2 import evalcontextfilter | ||
from os import path | ||
from sqlalchemy.orm.exc import NoResultFound | ||
from typing import Tuple | ||
|
||
import i18n | ||
|
@@ -19,12 +18,12 @@ | |
|
||
from crypto_util import CryptoUtil | ||
from db import db | ||
from models import InstanceConfig, Source | ||
from models import InstanceConfig | ||
from request_that_secures_file_uploads import RequestThatSecuresFileUploads | ||
from sdconfig import SDConfig | ||
from source_app import main, info, api | ||
from source_app.decorators import ignore_static | ||
from source_app.utils import logged_in, was_in_generate_flow | ||
from source_app.utils import clear_session_and_redirect_to_logged_out_page | ||
from store import Storage | ||
|
||
|
||
|
@@ -77,9 +76,6 @@ def setup_i18n() -> None: | |
# TODO: Attaching a CryptoUtil dynamically like this disables all type checking (and | ||
# breaks code analysis tools) for code that uses current_app.storage; it should be refactored | ||
app.crypto_util = CryptoUtil( | ||
scrypt_params=config.SCRYPT_PARAMS, | ||
scrypt_id_pepper=config.SCRYPT_ID_PEPPER, | ||
scrypt_gpg_pepper=config.SCRYPT_GPG_PEPPER, | ||
securedrop_root=config.SECUREDROP_ROOT, | ||
nouns_file=config.NOUNS, | ||
adjectives_file=config.ADJECTIVES, | ||
|
@@ -88,10 +84,7 @@ def setup_i18n() -> None: | |
|
||
@app.errorhandler(CSRFError) | ||
def handle_csrf_error(e: CSRFError) -> werkzeug.Response: | ||
msg = render_template('session_timeout.html') | ||
session.clear() | ||
flash(Markup(msg), "important") | ||
return redirect(url_for('main.index')) | ||
return clear_session_and_redirect_to_logged_out_page(session) | ||
|
||
assets = Environment(app) | ||
app.config['assets'] = assets | ||
|
@@ -139,57 +132,17 @@ def load_instance_config() -> None: | |
@app.before_request | ||
@ignore_static | ||
def setup_g() -> Optional[werkzeug.Response]: | ||
"""Store commonly used values in Flask's special g object""" | ||
|
||
if 'expires' in session and datetime.utcnow() >= session['expires']: | ||
msg = render_template('session_timeout.html') | ||
|
||
# Show expiration message only if the user was | ||
# either in the codename generation flow or logged in | ||
show_expiration_message = any([ | ||
session.get('show_expiration_message'), | ||
logged_in(), | ||
was_in_generate_flow(), | ||
]) | ||
|
||
# clear the session after we render the message so it's localized | ||
session.clear() | ||
|
||
# Persist this properety across sessions to distinguish users whose sessions expired | ||
# from users who never logged in or generated a codename | ||
session['show_expiration_message'] = show_expiration_message | ||
|
||
# Redirect to index with flashed message | ||
if session['show_expiration_message']: | ||
flash(Markup(msg), "important") | ||
return redirect(url_for('main.index')) | ||
# If the session as has expired, redirect to index with flashed message | ||
# if the user was in the middle of the generate flow | ||
if 'codenames' in session: | ||
return clear_session_and_redirect_to_logged_out_page(session) | ||
|
||
session['expires'] = datetime.utcnow() + \ | ||
timedelta(minutes=getattr(config, | ||
'SESSION_EXPIRATION_MINUTES', | ||
120)) | ||
|
||
# ignore_static here because `crypto_util.hash_codename` is scrypt | ||
# (very time consuming), and we don't need to waste time running if | ||
# we're just serving a static resource that won't need to access | ||
# these common values. | ||
if logged_in(): | ||
g.codename = session['codename'] | ||
g.filesystem_id = app.crypto_util.hash_codename(g.codename) | ||
try: | ||
g.source = Source.query \ | ||
.filter(Source.filesystem_id == g.filesystem_id) \ | ||
.filter_by(deleted_at=None) \ | ||
.one() | ||
except NoResultFound as e: | ||
app.logger.error( | ||
"Found no Sources when one was expected: %s" % | ||
(e,)) | ||
del session['logged_in'] | ||
del session['codename'] | ||
return redirect(url_for('main.index')) | ||
g.loc = app.storage.path(g.filesystem_id) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the authentication and session logic in this file is now handled by the SessionManager, which is called when the |
||
|
||
if app.instance_config.organization_name: | ||
g.organization_name = app.instance_config.organization_name | ||
else: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,28 @@ | ||
from typing import Any | ||
|
||
from flask import redirect, url_for, request | ||
from flask import redirect, url_for, request, session | ||
from functools import wraps | ||
|
||
from typing import Callable | ||
|
||
from source_app.utils import logged_in | ||
from source_app.utils import clear_session_and_redirect_to_logged_out_page | ||
from source_app.session_manager import SessionManager, UserNotLoggedIn, \ | ||
UserSessionExpired, UserHasBeenDeleted | ||
|
||
|
||
def login_required(f: Callable) -> Callable: | ||
@wraps(f) | ||
def decorated_function(*args: Any, **kwargs: Any) -> Any: | ||
if not logged_in(): | ||
return redirect(url_for('main.login')) | ||
return f(*args, **kwargs) | ||
try: | ||
logged_in_source = SessionManager.get_logged_in_user() | ||
|
||
except (UserSessionExpired, UserHasBeenDeleted): | ||
return clear_session_and_redirect_to_logged_out_page(session) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
except UserNotLoggedIn: | ||
return redirect(url_for("main.login")) | ||
|
||
return f(*args, **kwargs, logged_in_source=logged_in_source) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The decorator directly passes the source user to the Flask route/code (to make the "data flow" more obvious/simple), so using flask.g or session (and checking that they are in the right "state") is no longer needed. |
||
return decorated_function | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer needed since I removed the "time consuming" scrypt call from the code that handles incoming requests.