Skip to content

Commit c8faffa

Browse files
authored
Merge pull request #41 from ctreffe/release-v0.4.5
Release v0.4.5
2 parents 4ed472e + 87fddb6 commit c8faffa

File tree

15 files changed

+386
-304
lines changed

15 files changed

+386
-304
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ test.py
3030
test_copy.py
3131
test_copy2.py
3232
.vscode/.ropeproject/config.py
33+
.vscode/launch.json

NEWS.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
# Mortimer v0.4.5
2+
3+
## Security improvements
4+
5+
* We further increased data protection and data security through an improved handling of access to the alfred database from inside experiments.
6+
7+
## Bugfixes
8+
9+
* Fixed a bug that caused JavaScript to crash on some pages.
10+
* Fixed a bug that caused an error during data encryption using the key introduced in v0.4.4
11+
* Fixed a bug that prevented the deletion of experiments with specific filenames
12+
* Fixed the referrer after changing an experiments title
13+
14+
## Small changes
15+
16+
* Included line wrapping for the experiment log
17+
118
# Mortimer v0.4.4
219

320
## Encryption Keys
@@ -8,7 +25,7 @@
825

926
## Bugfixes
1027

11-
* Fixed a bug that prevent the deletion of single files in the resources pane to work properly.
28+
* Fixed a bug that prevented the deletion of single files in the resources pane to work properly.
1229

1330
# Mortimer v0.4.3
1431

mortimer/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from flask_dropzone import Dropzone
1010
import pymongo
1111

12-
__version__ = '0.4.4'
12+
__version__ = '0.4.5'
1313

1414
# the EnvironSetter sets enviroment variables for the current session
1515
# It is not included in the GitHub repository, because it contains sensitive
@@ -45,17 +45,17 @@
4545
db = MongoEngine() # mortimer database
4646

4747
# database for querying alfred collections
48-
client = pymongo.MongoClient(host=Config.MONGODB_ALFRED_SETTINGS["host"],
49-
port=Config.MONGODB_ALFRED_SETTINGS["port"],
50-
username=Config.MONGODB_ALFRED_SETTINGS["username"],
51-
password=Config.MONGODB_ALFRED_SETTINGS["password"],
52-
authSource=Config.MONGODB_ALFRED_SETTINGS["authentication_source"],
48+
db_config = Config.MONGODB_ALFRED_SETTINGS
49+
client = pymongo.MongoClient(host=db_config["host"],
50+
port=db_config["port"],
51+
username=db_config["username"],
52+
password=db_config["password"],
53+
authSource=db_config["authentication_source"],
5354
ssl=Config.mongodb_ssl,
5455
ssl_ca_certs=Config.ssl_ca_path
5556
)
5657

57-
alfred_db = client.alfred # checkin database
58-
alfred_web_db = alfred_db.web # web collection
58+
alfred_db = client[db_config["db"]] # checkin database
5959
alfred_local_db = alfred_db.local # local collection
6060

6161

mortimer/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class Config:
7171
DROPZONE_ALLOWED_FILE_TYPE = (
7272
".pdf, image/*, .txt, .xml, .pem, .mp3, .mp4, .ogg, .csv"
7373
)
74-
DROPZONE_MAX_FILE_SIZE = 10
74+
DROPZONE_MAX_FILE_SIZE = 100
7575
DROPZONE_MAX_FILES = 100
7676
DROPZONE_UPLOAD_ON_CLICK = True
7777
DROPZONE_UPLOAD_BTN_ID = "upload"

mortimer/local_experiments/routes.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
21
# -*- coding: utf-8 -*-
3-
4-
from mortimer import export
5-
from flask import Blueprint, render_template, url_for, flash, redirect, abort, send_file, request
6-
from mortimer.forms import ExperimentExportForm, LocalExperimentForm
7-
from mortimer.models import User, LocalExperiment
8-
from mortimer import alfred_local_db
9-
from mortimer.config import Config
10-
from flask_login import current_user, login_required
112
from datetime import datetime
123
from uuid import uuid4
134

5+
from flask import (Blueprint, abort, flash, redirect, render_template, request,
6+
send_file, url_for)
7+
from flask_login import current_user, login_required
8+
9+
from mortimer import alfred_local_db, export
10+
from mortimer.config import Config
11+
from mortimer.forms import ExperimentExportForm, LocalExperimentForm
12+
from mortimer.models import LocalExperiment, User
13+
1414
local_experiments = Blueprint("local_experiments", __name__)
1515

1616

@@ -27,13 +27,13 @@ def new_local_experiment():
2727
description=form.description.data, exp_id=form.exp_id.data)
2828

2929
experiment.save()
30-
flash("New local experiment added. You can now download the corresponding data.", "success")
30+
flash("New local experiment added.", "success")
3131
return redirect(url_for('local_experiments.local_experiment', username=current_user.username, experiment_title=experiment.title))
3232

3333
return render_template("create_local_experiment.html", form=form, legend="New Local Experiment", id=id)
3434

3535

36-
@local_experiments.route("/<username>/local/<path:experiment_title>", methods=["POST", "GET"])
36+
@local_experiments.route("/local/<username>/<path:experiment_title>", methods=["POST", "GET"])
3737
@login_required
3838
def local_experiment(username, experiment_title):
3939

@@ -76,8 +76,8 @@ def local_experiment(username, experiment_title):
7676
first_activity = "none"
7777
last_activity = "none"
7878

79-
if not versions:
80-
flash('Experiment not found in database.', 'danger')
79+
# if not versions:
80+
# flash('Experiment not found in database.', 'danger')
8181

8282
return render_template("local_experiment.html",
8383
experiment=experiment, expid=str(experiment.id), datasets=datasets,
@@ -112,7 +112,7 @@ def user_experiments(username):
112112
return render_template("user_local_experiments.html", experiments=experiments, user=user)
113113

114114

115-
@local_experiments.route("/<username>/<path:experiment_title>/local_export", methods=["POST", "GET"])
115+
@local_experiments.route("/local/<username>/<path:experiment_title>/export", methods=["POST", "GET"])
116116
@login_required
117117
def local_export(username, experiment_title):
118118
experiment = LocalExperiment.objects.get_or_404(
@@ -123,11 +123,12 @@ def local_export(username, experiment_title):
123123

124124
form = ExperimentExportForm()
125125
cur = alfred_local_db.find(
126-
{"exp_author": current_user.email, "exp_title": experiment.title})
127-
126+
{"exp_id": str(experiment.exp_id)})
127+
128128
available_versions = ["all versions"]
129129
for exp in cur:
130-
available_versions.append(exp["exp_version"])
130+
if exp["exp_version"] not in available_versions:
131+
available_versions.append(exp["exp_version"])
131132
form.version.choices = [(version, version)
132133
for version in available_versions]
133134
form.file_type.choices = [("csv", "csv")]
@@ -146,13 +147,28 @@ def local_export(username, experiment_title):
146147
else:
147148
for version in form.version.data:
148149
results = []
149-
results.append(alfred_local_db.count_documents(
150-
{"exp_id": experiment.exp_id, "exp_version": form.version.data}))
150+
results.append(
151+
alfred_local_db.count_documents(
152+
{"exp_id": experiment.exp_id, "exp_version": version}
153+
)
154+
)
151155
if max(results) == 0:
152156
flash("No data found for this experiment.", "warning")
153-
return redirect(url_for('web_experiments.web_export', username=experiment.author, experiment_title=experiment.title))
157+
return redirect(
158+
url_for(
159+
"web_experiments.web_export",
160+
username=experiment.author,
161+
experiment_title=experiment.title,
162+
)
163+
)
164+
165+
cur = alfred_local_db.find(
166+
{
167+
"exp_id": experiment.exp_id,
168+
"exp_version": {"$in": form.version.data},
169+
}
170+
)
154171

155-
cur = alfred_local_db.find({"exp_id": experiment.exp_id, "exp_version": {"$in": form.version.data}})
156172

157173
none_value = None
158174
# if form.replace_none.data:

mortimer/models.py

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
# -*- coding: utf-8 -*-
2+
import random
3+
import string
4+
from datetime import datetime
25

6+
from pymongo import MongoClient
7+
from cryptography.fernet import Fernet
38
from flask import current_app
9+
from flask_login import UserMixin
10+
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
11+
from alfred import settings as alfred_settings
12+
413
from mortimer import db, login_manager
514
from mortimer.utils import create_fernet
6-
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
7-
from datetime import datetime
8-
from flask_login import UserMixin
9-
from cryptography.fernet import Fernet
15+
16+
# pylint: disable=no-member
1017

1118

1219
@login_manager.user_loader
@@ -22,17 +29,60 @@ class User(db.Document, UserMixin):
2229
password = db.StringField(required=True)
2330
experiments = db.ListField(db.ObjectIdField())
2431

32+
alfred_user = db.StringField()
33+
alfred_pw = db.BinaryField()
34+
alfred_col = db.StringField()
35+
2536
def get_reset_token(self, expires_sec=1800):
2637
# methode for creating a token for password reset
2738
s = Serializer(current_app.config["SECRET_KEY"], expires_sec)
2839
return s.dumps({"user_id": str(self.id)}).decode("utf-8")
29-
40+
3041
@staticmethod
31-
def generate_encryption_key():
42+
def generate_encryption_key() -> bytes:
43+
"""Generate a new fernet encryption key and encrypt it with the apps secret fernet key.
44+
"""
3245
key = Fernet.generate_key()
3346
f = create_fernet()
3447
encrypted_key = f.encrypt(key)
3548
return encrypted_key
49+
50+
@staticmethod
51+
def generate_password() -> bytes:
52+
"""Generate a random password and encrypt it with the apps secret fernet key.
53+
"""
54+
f = create_fernet()
55+
letters = string.ascii_lowercase
56+
pw_raw = "".join(random.choice(letters) for i in range(20))
57+
pw_enc = f.encrypt(pw_raw.encode())
58+
return pw_enc
59+
60+
def create_db_user(self):
61+
"""Create a new user in the alfred database.
62+
"""
63+
alfred_db = current_app.config["MONGODB_ALFRED_SETTINGS"]["db"]
64+
user_lower = self.username.lower().replace(" ", "_")
65+
rolename = "alfredAccess_{}".format(user_lower)
66+
res = {"db": alfred_db, "collection": self.alfred_col}
67+
act = ["find", "insert", "update"]
68+
priv = [{"resource": res, "actions": act}]
69+
70+
cred = current_app.config["MONGODB_SETTINGS"]
71+
72+
client = MongoClient(
73+
host=cred["host"],
74+
port=cred["port"],
75+
username=cred["username"],
76+
password=cred["password"],
77+
ssl=cred["ssl"],
78+
ssl_ca_certs=cred["ssl_ca_certs"]
79+
)
80+
81+
client.alfred.command("createRole", rolename, privileges=priv, roles=[])
82+
83+
client.alfred.command(
84+
"createUser", self.alfred_user, pwd=self.alfred_pw, roles=[rolename]
85+
)
3686

3787
@staticmethod
3888
def verify_reset_token(token):
@@ -51,6 +101,7 @@ def __repr__(self):
51101

52102
class WebExperiment(db.Document):
53103
author = db.StringField(required=True)
104+
author_id = db.ObjectIdField()
54105
title = db.StringField(required=True, unique_with="author")
55106
version = db.StringField(required=True)
56107
available_versions = db.ListField(db.StringField())
@@ -75,6 +126,37 @@ class WebExperiment(db.Document):
75126
web = db.BooleanField()
76127
active = db.BooleanField(default=False)
77128

129+
def set_settings(self):
130+
"""Set experiment settings based on self and alfred.settings.
131+
"""
132+
if not self.id:
133+
raise AttributeError("The experiment needs to have an ID before settings can be set.")
134+
135+
exp_specific_settings = alfred_settings.ExperimentSpecificSettings()
136+
137+
settings = {
138+
'general': dict(alfred_settings.general),
139+
140+
'experiment': {
141+
'title': self.title,
142+
'author': self.author,
143+
'version': self.version,
144+
'type': alfred_settings.experiment.type,
145+
'exp_id': str(self.id),
146+
'qt_fullscreen': alfred_settings.experiment.qt_full_screen,
147+
'web_layout': alfred_settings.experiment.web_layout
148+
},
149+
150+
'mortimer_specific': {'session_id': None, 'path': self.path},
151+
'log': dict(alfred_settings.log),
152+
'navigation': dict(exp_specific_settings.navigation),
153+
'debug': dict(exp_specific_settings.debug), #pylint: disable=no-member
154+
'hints': dict(exp_specific_settings.hints),
155+
'messages': dict(exp_specific_settings.messages)
156+
}
157+
158+
self.settings = settings
159+
78160
def __repr__(self):
79161
return "Experiment(Title: %s, Version: %s, Created: %s, Author: %s)" % (
80162
self.title,

mortimer/static/futurizing_alfred_scripts/06_custom_modifications.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"set_level": "setLevel",
99
"pageGroup": "section",
1010
"WebCompositePage": "Page",
11-
"PageGroup": "Section"
12-
11+
"PageGroup": "Section",
12+
"appends": "append"
1313
}

0 commit comments

Comments
 (0)