Commit 7ad3dbf23b74b7e3c2807df284a651312d657f7c

Authored by hitier
2 parents 57125da2 149f7875

Password Encryption

Passwords are now encrypted in database.
Thank you to Olivier Thauvin <olivier.thauvin@latmos.ipsl.fr> for bug
report.
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"))