From 9d80fdab699ac26373586d98a5108bdb04f83196 Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Fri, 10 Apr 2020 15:49:16 +0200 Subject: [PATCH 1/6] add user-specific encryption key * Every user now has a unique fernet encryption key, generated via `cryptography.fernet.Fernet.generate_key()`. The key is passed as an entry in the `config` dictionary (key: `encryption_key`) to the `generate_experiment()` function in `alfredo.py`. * The key itself is saved to the mortimer database in encrypted form. --- NEWS.md | 8 ++++++++ mortimer/config.py | 13 +++++++++---- mortimer/models.py | 10 ++++++++++ mortimer/templates/account.html | 11 ++++++++--- mortimer/users/routes.py | 9 ++++++++- mortimer/utils.py | 7 ++++++- mortimer/web_experiments/alfredo.py | 8 ++++++++ 7 files changed, 57 insertions(+), 9 deletions(-) diff --git a/NEWS.md b/NEWS.md index d106087..02b92da 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# Mortimer v0.4.4 + +## Encryption Keys + +* Every user now has a unique fernet encryption key, generated via `cryptography.fernet.Fernet.generate_key()`. The key is passed as an entry in the `config` dictionary (key: `encryption_key`) to the `generate_experiment()` function in `alfredo.py`. +* The key itself is saved to the mortimer database in encrypted form. +* **Usage**: In your script.py, you can encrypt and decrypt data via `alfred.Experiment.encryptor` (**alfred v1.0.6**+), which is an instance of `cryptography.fernet.Fernet`, initialized with the key. It offers the methods `alfred.Experiment.decryptor.encrypt()` and `alfred.Experiment.decryptor.decrypt()`. **Note** that these methods require input of type `bytes` and return data of type `bytes`, so make sure to convert your data when using it. See the [Fernet documentation](https://cryptography.io/en/latest/fernet/) for more. + # Mortimer v0.4.3 ## Bugfixes diff --git a/mortimer/config.py b/mortimer/config.py index d46dd51..ca885bf 100644 --- a/mortimer/config.py +++ b/mortimer/config.py @@ -21,9 +21,12 @@ class Config: ssl_ca_certs = None ssl_ca_path = None - SECRET_KEY = os.environ.get( - "SECRET_KEY" - ) # secret key of flask app (e.g. for encrypted session data) + # secret key of app (e.g. for encrypted session data) + SECRET_KEY = os.environ.get("SECRET_KEY") + + # URL-safe base64-encoded 32-byte key for fernet encryption + FERNET_KEY = os.environ.get("FERNET_KEY") + PAROLE = os.environ.get("PAROLE") # Parole/Passphrase for registration EXP_PER_PAGE = 10 # number of experiments displayed per page @@ -65,7 +68,9 @@ class Config: # Flask-Dropzone settings: DROPZONE_ALLOWED_FILE_CUSTOM = True - DROPZONE_ALLOWED_FILE_TYPE = ".pdf, image/*, .txt, .xml, .pem, .mp3, .mp4, .ogg, .csv" + DROPZONE_ALLOWED_FILE_TYPE = ( + ".pdf, image/*, .txt, .xml, .pem, .mp3, .mp4, .ogg, .csv" + ) DROPZONE_MAX_FILE_SIZE = 10 DROPZONE_MAX_FILES = 100 DROPZONE_UPLOAD_ON_CLICK = True diff --git a/mortimer/models.py b/mortimer/models.py index 100730a..17a041c 100644 --- a/mortimer/models.py +++ b/mortimer/models.py @@ -2,9 +2,11 @@ from flask import current_app from mortimer import db, login_manager +from mortimer.utils import create_fernet from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from datetime import datetime from flask_login import UserMixin +from cryptography.fernet import Fernet @login_manager.user_loader @@ -16,6 +18,7 @@ class User(db.Document, UserMixin): username = db.StringField(required=True, unique=True, max_length=20) email = db.EmailField(required=True, unique=True) role = db.StringField(required=True, default="user") + encryption_key = db.BinaryField() password = db.StringField(required=True) experiments = db.ListField(db.ObjectIdField()) @@ -23,6 +26,13 @@ def get_reset_token(self, expires_sec=1800): # methode for creating a token for password reset s = Serializer(current_app.config["SECRET_KEY"], expires_sec) return s.dumps({"user_id": str(self.id)}).decode("utf-8") + + @staticmethod + def generate_encryption_key(): + key = Fernet.generate_key() + f = create_fernet() + encrypted_key = f.encrypt(key) + return encrypted_key @staticmethod def verify_reset_token(token): diff --git a/mortimer/templates/account.html b/mortimer/templates/account.html index 6552602..4347602 100644 --- a/mortimer/templates/account.html +++ b/mortimer/templates/account.html @@ -3,13 +3,18 @@ {% block content %}
-
- -
{{ form.hidden_tag() }}
Account Info + + {% if current_user.encryption_key %} +

Your account is ready for using encryption in your alfred + experiments.

+ {% else %} +

Your account is not ready for using encryption in your alfred + experiments. Please log out and log back in again to get ready!

+ {% endif %}
{{ form.username.label(class="form-control-label") }} diff --git a/mortimer/users/routes.py b/mortimer/users/routes.py index c27f4c4..b38df34 100644 --- a/mortimer/users/routes.py +++ b/mortimer/users/routes.py @@ -25,6 +25,7 @@ def register(): if form.validate_on_submit(): hashed_password = bcrypt.generate_password_hash(form.password.data).decode("utf-8") user = User(username=form.username.data, email=form.email.data, password=hashed_password) + user.encryption_key = User.generate_encryption_key() user.save() flash("Account created for %s." % form.username.data, "success") return redirect(url_for('users.login')) @@ -52,6 +53,12 @@ def login(): login_user(user, remember=form.remember.data) next_page = request.args.get("next") flash("Login Successful.", "success") + + # for backwards compatibility, generate encryption key if there isnt already one + if not user.encryption_key: + user.encryption_key = User.generate_encryption_key() + user.save() + return redirect(next_page) if next_page else redirect(url_for('main.home')) else: flash("Login Unsuccessful. Please check username and password", "danger") @@ -84,7 +91,7 @@ def account(): form.username.data = current_user.username form.email.data = current_user.email - return render_template("account.html", title="Account", form=form) + return render_template("account.html", title="Account", form=form, user = current_user) @users.route("/reset_password", methods=["GET", "POST"]) diff --git a/mortimer/utils.py b/mortimer/utils.py index 3f49e7a..161a879 100644 --- a/mortimer/utils.py +++ b/mortimer/utils.py @@ -9,9 +9,14 @@ from flask import current_app from mortimer import mail from flask_mail import Message -from flask import url_for +from flask import url_for, current_app from uuid import uuid4 from jinja2 import Template +from cryptography.fernet import Fernet + +def create_fernet(): + app_fernet_key = current_app.config["FERNET_KEY"].encode() + return Fernet(app_fernet_key) def set_experiment_settings(title, version, author, exp_id, path): diff --git a/mortimer/web_experiments/alfredo.py b/mortimer/web_experiments/alfredo.py index 17d67b3..1330777 100644 --- a/mortimer/web_experiments/alfredo.py +++ b/mortimer/web_experiments/alfredo.py @@ -9,6 +9,7 @@ from time import time from uuid import uuid4 from mortimer.models import WebExperiment +from mortimer.utils import create_fernet from bson.objectid import ObjectId from alfred.alfredlog import init_logging, getLogger import re @@ -160,6 +161,13 @@ def start(expid): custom_settings = experiment.settings custom_settings['mortimer_specific']['session_id'] = sid + + if current_user.encryption_key: + f = create_fernet() + key = f.decrypt(current_user.encryption_key) + custom_settings["encryption_key"] = key + else: + flash("Please log out and back in again to generate your personal encryption key.", "warning") try: if number_of_func_params(module.generate_experiment) > 2: From 4531c476e69621b7671719e3616f6015948d541c Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Fri, 10 Apr 2020 17:51:12 +0200 Subject: [PATCH 2/6] fix file deletion also includes some small refactoring --- .../additional/display_dir_controls.html | 62 ++++++++++++ .../additional/display_one_file.html | 35 +++++++ mortimer/utils.py | 96 +------------------ 3 files changed, 102 insertions(+), 91 deletions(-) create mode 100644 mortimer/templates/additional/display_dir_controls.html create mode 100644 mortimer/templates/additional/display_one_file.html diff --git a/mortimer/templates/additional/display_dir_controls.html b/mortimer/templates/additional/display_dir_controls.html new file mode 100644 index 0000000..cd523e9 --- /dev/null +++ b/mortimer/templates/additional/display_dir_controls.html @@ -0,0 +1,62 @@ +
+

+ {{path}} + + + + Upload Files + + +

+ +
+ +
+
+ + + +
+ +
+ {{file_display|safe}}
+
+ + + {{subdirectory_display}} + +
+ + \ No newline at end of file diff --git a/mortimer/templates/additional/display_one_file.html b/mortimer/templates/additional/display_one_file.html new file mode 100644 index 0000000..df663aa --- /dev/null +++ b/mortimer/templates/additional/display_one_file.html @@ -0,0 +1,35 @@ + +
+
+ - {{f}} +
+
+ +
+
+ + + \ No newline at end of file diff --git a/mortimer/utils.py b/mortimer/utils.py index 161a879..b35ab8a 100644 --- a/mortimer/utils.py +++ b/mortimer/utils.py @@ -9,7 +9,7 @@ from flask import current_app from mortimer import mail from flask_mail import Message -from flask import url_for, current_app +from flask import url_for, current_app, render_template from uuid import uuid4 from jinja2 import Template from cryptography.fernet import Fernet @@ -115,39 +115,9 @@ def display_files(file_list: list, path: str, experiment_title=experiment_title, username=experiment_author, relative_path=filepath) - display_one = Template('''
-
- - {{f}} -
-
Delete -
-
- - - ''') - - out.append(display_one.render(f=f, filepath=filepath, url=url)) + fileid = "{}_{}".format(f.replace(".", ""), str(uuid4())) + single_file = render_template("additional/display_one_file.html", f=f, fileid=fileid, url=url) + out.append(single_file) return "".join(out) def display_directory_controls(directory: str, @@ -173,63 +143,7 @@ def display_directory_controls(directory: str, username=experiment_author, relative_path=path) - out = Template('''
-

- {{path}} - - - - Upload Files - - -

- -
-
-
-
- - -
-
- -
- {{file_display}}
-
- - - {{subdirectory_display}} - -
- - - ''') - - return out.render(path=path, + return render_template("additional/display_dir_controls.html", path=path, directory=directory, upload_url=upload_url, call_id=call_id, From 8ebbc7cbd49e5c0ccf21c7c234b9d334e0ef7f5c Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Tue, 14 Apr 2020 17:55:13 +0200 Subject: [PATCH 3/6] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d8b1193..bbda0ed 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ alfred.conf exp/ .vscode/settings.json test.py +test_copy.py +test_copy2.py From 7d7cc3ac04d66a1dd0686c7b409bbb56ef923bf8 Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Tue, 14 Apr 2020 17:55:23 +0200 Subject: [PATCH 4/6] Update display_dir_controls.html --- mortimer/templates/additional/display_dir_controls.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mortimer/templates/additional/display_dir_controls.html b/mortimer/templates/additional/display_dir_controls.html index cd523e9..043e8e2 100644 --- a/mortimer/templates/additional/display_dir_controls.html +++ b/mortimer/templates/additional/display_dir_controls.html @@ -3,8 +3,7 @@ {{path}}
From ca1b78706b307dd3b7cc0b1d504c3a482843b05f Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Tue, 14 Apr 2020 17:57:39 +0200 Subject: [PATCH 5/6] increase version number --- mortimer/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mortimer/__init__.py b/mortimer/__init__.py index 4bfd8c5..e950ea3 100644 --- a/mortimer/__init__.py +++ b/mortimer/__init__.py @@ -9,7 +9,7 @@ from flask_dropzone import Dropzone import pymongo -__version__ = '0.4.3' +__version__ = '0.4.4' # the EnvironSetter sets enviroment variables for the current session # It is not included in the GitHub repository, because it contains sensitive From 384439ac4d76b45516644b6011f34be1e55ebe32 Mon Sep 17 00:00:00 2001 From: Johannes Brachem Date: Tue, 14 Apr 2020 17:57:41 +0200 Subject: [PATCH 6/6] Update NEWS.md --- NEWS.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 02b92da..fd4145f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,11 @@ * Every user now has a unique fernet encryption key, generated via `cryptography.fernet.Fernet.generate_key()`. The key is passed as an entry in the `config` dictionary (key: `encryption_key`) to the `generate_experiment()` function in `alfredo.py`. * The key itself is saved to the mortimer database in encrypted form. -* **Usage**: In your script.py, you can encrypt and decrypt data via `alfred.Experiment.encryptor` (**alfred v1.0.6**+), which is an instance of `cryptography.fernet.Fernet`, initialized with the key. It offers the methods `alfred.Experiment.decryptor.encrypt()` and `alfred.Experiment.decryptor.decrypt()`. **Note** that these methods require input of type `bytes` and return data of type `bytes`, so make sure to convert your data when using it. See the [Fernet documentation](https://cryptography.io/en/latest/fernet/) for more. +* Usage: See [here](https://github.com/ctreffe/alfred/releases/tag/v1.0.6) + +## Bugfixes + +* Fixed a bug that prevent the deletion of single files in the resources pane to work properly. # Mortimer v0.4.3