Commit 7ad3dbf23b74b7e3c2807df284a651312d657f7c
Exists in
master
and in
4 other branches
Password Encryption
Passwords are now encrypted in database. Thank you to Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr> for bug report.
Showing
5 changed files
with
65 additions
and
14 deletions
Show diff stats
app/auth/models.py
1 | from pprint import pprint | 1 | from pprint import pprint |
2 | 2 | ||
3 | -from flask_login import UserMixin, current_user | 3 | +from werkzeug.security import generate_password_hash, check_password_hash |
4 | +from flask_login import UserMixin | ||
4 | from app.models import db | 5 | from app.models import db |
5 | 6 | ||
6 | # | 7 | # |
@@ -47,8 +48,9 @@ class User(UserMixin, db.Model): | @@ -47,8 +48,9 @@ class User(UserMixin, db.Model): | ||
47 | email = db.Column(db.String(100), unique=True) | 48 | email = db.Column(db.String(100), unique=True) |
48 | name = db.Column(db.String(100)) | 49 | name = db.Column(db.String(100)) |
49 | login = db.Column(db.String(100), unique=True) | 50 | login = db.Column(db.String(100), unique=True) |
50 | - password = db.Column(db.String(100)) | ||
51 | role = db.Column(db.Integer, default=0) | 51 | role = db.Column(db.Integer, default=0) |
52 | + password = db.Column(db.String(128)) | ||
53 | + password_hash = db.Column(db.String(128)) | ||
52 | 54 | ||
53 | def __repr__(self): | 55 | def __repr__(self): |
54 | return "i: {}, n: {}, e: {}, l: {}".format(self.id, self.name, self.email, self.login) | 56 | return "i: {}, n: {}, e: {}, l: {}".format(self.id, self.name, self.email, self.login) |
@@ -56,7 +58,11 @@ class User(UserMixin, db.Model): | @@ -56,7 +58,11 @@ class User(UserMixin, db.Model): | ||
56 | # Set role at construction time | 58 | # Set role at construction time |
57 | def __init__(self, **kwargs): | 59 | def __init__(self, **kwargs): |
58 | super(User, self).__init__(**kwargs) | 60 | super(User, self).__init__(**kwargs) |
59 | - self.set_role(kwargs['role']) | 61 | + if ('role' in kwargs): |
62 | + self.set_role(kwargs['role']) | ||
63 | + if ('password' in kwargs): | ||
64 | + self.set_password(kwargs['password']) | ||
65 | + self.password = None | ||
60 | 66 | ||
61 | def set_role(self, role): | 67 | def set_role(self, role): |
62 | self.role = _checkRole(role) | 68 | self.role = _checkRole(role) |
@@ -68,3 +74,9 @@ class User(UserMixin, db.Model): | @@ -68,3 +74,9 @@ class User(UserMixin, db.Model): | ||
68 | def has_role_or_higher(self, role): | 74 | def has_role_or_higher(self, role): |
69 | role = _checkRole(role) | 75 | role = _checkRole(role) |
70 | return self.role and (self.role >= role) | 76 | return self.role and (self.role >= role) |
77 | + | ||
78 | + def set_password(self, password): | ||
79 | + self.password_hash = generate_password_hash(password) | ||
80 | + | ||
81 | + def check_password(self, password): | ||
82 | + return check_password_hash(self.password_hash, password) |
app/auth/routes.py
@@ -52,7 +52,7 @@ def login_post(): | @@ -52,7 +52,7 @@ def login_post(): | ||
52 | user_password = request.form.get('password') | 52 | user_password = request.form.get('password') |
53 | # user_remember = request.form.get('remember') | 53 | # user_remember = request.form.get('remember') |
54 | user = User.query.filter_by(login=user_login).one_or_none() | 54 | user = User.query.filter_by(login=user_login).one_or_none() |
55 | - if user and user.password == user_password: | 55 | + if user and user.check_password(user_password): |
56 | login_user(user) | 56 | login_user(user) |
57 | flash("Connection Réussie !", 'success') | 57 | flash("Connection Réussie !", 'success') |
58 | return redirect(url_for('main.index')) | 58 | return redirect(url_for('main.index')) |
app/commands/commands.py
@@ -12,7 +12,7 @@ from sqlalchemy.orm import Session | @@ -12,7 +12,7 @@ from sqlalchemy.orm import Session | ||
12 | from sqlalchemy import create_engine | 12 | from sqlalchemy import create_engine |
13 | 13 | ||
14 | from app.models import db, Agent, Service, Project, Capacity, Period, Charge | 14 | from app.models import db, Agent, Service, Project, Capacity, Period, Charge |
15 | -from app.auth.models import User | 15 | +from app.auth.models import User, _nameToRole |
16 | 16 | ||
17 | from . import bp | 17 | from . import bp |
18 | 18 | ||
@@ -199,7 +199,8 @@ def create_db(): | @@ -199,7 +199,8 @@ def create_db(): | ||
199 | configure the proper database uri in the db_config.py file. | 199 | configure the proper database uri in the db_config.py file. |
200 | """ | 200 | """ |
201 | db.create_all() | 201 | db.create_all() |
202 | - admin = User(email='admin@nowhere.org', name='admin', login='admin', password='admin', role='admin') | 202 | + admin = User(email='admin@nowhere.org', name='admin', login='admin', role='admin') |
203 | + admin.set_password('admin') | ||
203 | sqlite_uri = db.engine.url.__str__() if 'sqlite' in db.engine.url.__str__() else None | 204 | sqlite_uri = db.engine.url.__str__() if 'sqlite' in db.engine.url.__str__() else None |
204 | try: | 205 | try: |
205 | db.session.add(admin) | 206 | db.session.add(admin) |
@@ -219,12 +220,23 @@ def create_db(): | @@ -219,12 +220,23 @@ def create_db(): | ||
219 | @click.argument('name') | 220 | @click.argument('name') |
220 | @click.argument('login') | 221 | @click.argument('login') |
221 | @click.argument('password') | 222 | @click.argument('password') |
222 | -def user_add(email, name, login, password): | 223 | +@click.argument('role') |
224 | +def user_add(email, name, login, password, role): | ||
223 | """ Add a new user in db.""" | 225 | """ Add a new user in db.""" |
224 | - user = User(email=email, name=name, login=login, password=password) | 226 | + user = User.query.filter(User.name == name).one_or_none() |
227 | + if (user): | ||
228 | + current_app.logger.error(f"user already exists {name}") | ||
229 | + return | ||
230 | + user = User(email=email, name=name, login=login, password=password, role=role) | ||
225 | db.session.add(user) | 231 | db.session.add(user) |
226 | db.session.commit() | 232 | db.session.commit() |
227 | - current_app.logger.info("added ", name) | 233 | + current_app.logger.info(f"added {name}") |
234 | + | ||
235 | + | ||
236 | +@bp.cli.command('show_roles') | ||
237 | +def show_roles(): | ||
238 | + """ List all available roles for a user""" | ||
239 | + print("\n".join(list(_nameToRole))) | ||
228 | 240 | ||
229 | 241 | ||
230 | @bp.cli.command('user_show_all') | 242 | @bp.cli.command('user_show_all') |
resources/pdc_config.py
@@ -102,7 +102,7 @@ class DevConfig(Config): | @@ -102,7 +102,7 @@ class DevConfig(Config): | ||
102 | class TestConfig(Config): | 102 | class TestConfig(Config): |
103 | TESTING = True | 103 | TESTING = True |
104 | DEBUG = True | 104 | DEBUG = True |
105 | - PDC_LOGS_LEVEL = 'ERROR' # choose within DEBUG, INFO, WARN, ERROR ( more levels in logging module ) | 105 | + PDC_LOGS_LEVEL = 'INFO' # choose within DEBUG, INFO, WARN, ERROR ( more levels in logging module ) |
106 | 106 | ||
107 | # Trying to set specific db uri from ./db_config.py | 107 | # Trying to set specific db uri from ./db_config.py |
108 | try: | 108 | try: |
tests/backend_tests.py
@@ -11,7 +11,8 @@ class BaseTestCase(unittest.TestCase): | @@ -11,7 +11,8 @@ class BaseTestCase(unittest.TestCase): | ||
11 | self.app = create_app(TestConfig) | 11 | self.app = create_app(TestConfig) |
12 | self.app_context = self.app.app_context() | 12 | self.app_context = self.app.app_context() |
13 | self.app_context.push() | 13 | self.app_context.push() |
14 | - # TODO: shall we always copy db from resources ? | 14 | + # TODO: shall we always copy db from resources sqlite file ? |
15 | + # that would allow us to run all tests in sqlite:memory: | ||
15 | # db.create_all() | 16 | # db.create_all() |
16 | # admin = User(email='admin@nowhere.org', name='admin', login='admin', password='admin', role='admin') | 17 | # admin = User(email='admin@nowhere.org', name='admin', login='admin', password='admin', role='admin') |
17 | # db.session.add(admin) | 18 | # db.session.add(admin) |
@@ -48,16 +49,42 @@ class DbMgrTestCase(BaseTestCase): | @@ -48,16 +49,42 @@ class DbMgrTestCase(BaseTestCase): | ||
48 | 49 | ||
49 | class AuthModelTestCase(BaseTestCase): | 50 | class AuthModelTestCase(BaseTestCase): |
50 | 51 | ||
52 | + def skip_if_no_sqlitememory(self): | ||
53 | + if 'memory' not in self.app.config['SQLALCHEMY_DATABASE_URI']: | ||
54 | + self.skipTest("Needs in memory sqlite") | ||
55 | + | ||
56 | + def get_admin(self): | ||
57 | + return User.query.filter(User.name == 'admin').one() | ||
58 | + | ||
59 | + def setUp(self): | ||
60 | + BaseTestCase.setUp(self) | ||
61 | + self.skip_if_no_sqlitememory() | ||
62 | + db.create_all() | ||
63 | + admin = User(email='admin@nowhere.org', name='admin', login='admin', role='admin') | ||
64 | + db.session.add(admin) | ||
65 | + db.session.commit() | ||
66 | + | ||
67 | + def test_in_memory(self): | ||
68 | + self.app.logger.info("In memory Sqlite DB for tests") | ||
69 | + self.assertTrue(True) | ||
70 | + | ||
51 | def test_setrole(self): | 71 | def test_setrole(self): |
52 | - admin = User.query.filter(User.name == 'admin').one_or_none() | 72 | + admin = self.get_admin() |
53 | admin.set_role("ADMIN") | 73 | admin.set_role("ADMIN") |
54 | db.session.commit() | 74 | db.session.commit() |
55 | - admin = User.query.filter(User.name == 'admin').one_or_none() | 75 | + admin = self.get_admin() |
56 | self.assertTrue(admin is not None) | 76 | self.assertTrue(admin is not None) |
57 | self.assertTrue(admin.has_role("ADMIN")) | 77 | self.assertTrue(admin.has_role("ADMIN")) |
58 | self.assertFalse(admin.has_role("SERVICE")) | 78 | self.assertFalse(admin.has_role("SERVICE")) |
59 | 79 | ||
60 | def test_setrole_valueerror(self): | 80 | def test_setrole_valueerror(self): |
61 | - admin = User(email='me@nowhere.org', name='me', login='me', password='me', role='admin') | 81 | + admin = self.get_admin() |
62 | with self.assertRaises(ValueError) as ve: | 82 | with self.assertRaises(ValueError) as ve: |
63 | admin.set_role("NOSUCHROLE") | 83 | admin.set_role("NOSUCHROLE") |
84 | + | ||
85 | + def test_setcheckpassword(self): | ||
86 | + admin = self.get_admin() | ||
87 | + admin.set_password("hahaha") | ||
88 | + db.session.commit() | ||
89 | + admin2 = self.get_admin() | ||
90 | + self.assertTrue(admin2.check_password("hahaha")) |