minor nach master vor 5 Jahren zusammengeführt
| @@ -2,7 +2,7 @@ | |||
| CREATE TABLE users ( | |||
| userid INT UNSIGNED AUTO_INCREMENT, | |||
| username VARCHAR(45) UNIQUE NOT NULL, | |||
| password VARCHAR(45) NOT NULL, | |||
| password VARCHAR(60) NOT NULL, | |||
| full_name VARCHAR(200) NOT NULL, | |||
| company VARCHAR(50), | |||
| email VARCHAR(50) NOT NULL, | |||
| @@ -84,7 +84,7 @@ CREATE TABLE task_files ( | |||
| * Initial data | |||
| */ | |||
| insert into users values (NULL, "admin", "48bead1bb864138c2cafaf1bd41332ab", "Admin Modsen", "ntnu", 'mail@ntnu.no', "street", "trondheim", "trondheim", "1234", "norway"); | |||
| insert into users values (NULL, "admin", "$2b$12$iKbYZ0MFwWWxoYUXKRhFiOPo7itaQO2DIRnLgXbECsj8XKVzkNCSi", "Admin Modsen", "ntnu", 'mail@ntnu.no', "street", "trondheim", "trondheim", "1234", "norway"); | |||
| insert into project_category values (NULL, "Gardening"); | |||
| insert into project_category values (NULL, "Programming"); | |||
| @@ -4,10 +4,11 @@ FROM tiangolo/uwsgi-nginx:python3.7 | |||
| ENV LISTEN_PORT 8080 | |||
| EXPOSE 8080 | |||
| # Define environment | |||
| COPY ./app /app | |||
| RUN echo ${groupid} > /app/models/.env | |||
| WORKDIR /app | |||
| # The requirements file is copied first, so if there are no changes | |||
| # docker will skip installing them again, and use a cached image instead | |||
| COPY ./app/requirements.txt /app/ | |||
| ENV PYTHONPATH=/app | |||
| # Install python dependencies | |||
| @@ -23,6 +24,10 @@ COPY entrypoint.sh /entrypoint.sh | |||
| RUN chmod +x /entrypoint.sh | |||
| ENTRYPOINT ["/entrypoint.sh"] | |||
| # Copy the rest of the project files and define the environment | |||
| COPY ./app /app | |||
| RUN echo ${groupid} > /app/models/.env | |||
| # Allow waiting script to be executed | |||
| RUN chmod +x ./wait-for-it.sh | |||
| @@ -2,6 +2,33 @@ from models.database import db | |||
| import mysql.connector | |||
| def get_user(username): | |||
| """ | |||
| Get the user with the given username | |||
| :param username: The username | |||
| :type username: str | |||
| :return: user | |||
| """ | |||
| db.connect() | |||
| cursor = db.cursor() | |||
| query = ("SELECT userid, username, password from users where username = %s") | |||
| user = None | |||
| try: | |||
| cursor.execute(query, (username,)) | |||
| users = cursor.fetchall() | |||
| if len(users): | |||
| user = users[0] | |||
| except mysql.connector.Error as err: | |||
| print("Failed executing query: {}".format(err)) | |||
| cursor.fetchall() | |||
| exit(1) | |||
| finally: | |||
| cursor.close() | |||
| db.close() | |||
| return user | |||
| def get_users(): | |||
| """ | |||
| Retreive all registrered users from the database | |||
| @@ -73,32 +100,3 @@ def get_user_name_by_id(userid): | |||
| cursor.close() | |||
| db.close() | |||
| return username | |||
| def match_user(username, password): | |||
| """ | |||
| Check if user credentials are correct, return if exists | |||
| :param username: The user attempting to authenticate | |||
| :param password: The corresponding password | |||
| :type username: str | |||
| :type password: str | |||
| :return: user | |||
| """ | |||
| db.connect() | |||
| cursor = db.cursor() | |||
| query = ("SELECT userid, username from users where username = %s and password = %s") | |||
| user = None | |||
| try: | |||
| cursor.execute(query, (username, password)) | |||
| users = cursor.fetchall() | |||
| if len(users): | |||
| user = users[0] | |||
| except mysql.connector.Error as err: | |||
| print("Failed executing query: {}".format(err)) | |||
| cursor.fetchall() | |||
| exit(1) | |||
| finally: | |||
| cursor.close() | |||
| db.close() | |||
| return user | |||
| @@ -1,3 +1,4 @@ | |||
| web.py==0.40 | |||
| mysql-connector-python==8.0.* | |||
| python-dotenv | |||
| bcrypt | |||
| @@ -5,7 +5,7 @@ import models.session | |||
| import models.user | |||
| import random | |||
| import string | |||
| import hashlib | |||
| import bcrypt | |||
| import time | |||
| # Get html templates | |||
| @@ -45,11 +45,9 @@ class Login(): | |||
| data = web.input(username="", password="", remember=False) | |||
| # Validate login credential with database query | |||
| password_hash = hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest() | |||
| user = models.user.match_user(data.username, password_hash) | |||
| user = models.user.get_user(data.username) | |||
| # If there is a matching user/password in the database the user is logged in | |||
| if user: | |||
| if bcrypt.checkpw(data.password.encode('UTF-8'), user[2].encode('UTF-8')): | |||
| self.login(user[1], user[0], data.remember) | |||
| raise web.seeother("/") | |||
| else: | |||
| @@ -1,9 +1,9 @@ | |||
| import web | |||
| from views.forms import register_form | |||
| from views.utils import get_nav_bar, csrf_protected | |||
| from views.utils import get_nav_bar, csrf_protected, password_weakness | |||
| import models.register | |||
| import models.user | |||
| import hashlib | |||
| import bcrypt | |||
| import re | |||
| # Get html templates | |||
| @@ -41,9 +41,15 @@ class Register: | |||
| if models.user.get_user_id_by_name(data.username): | |||
| return render.register(nav, register, "Invalid user, already exists.") | |||
| models.register.set_user(data.username, | |||
| hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest(), | |||
| data.full_name, data.company, data.email, data.street_address, | |||
| data.city, data.state, data.postal_code, data.country) | |||
| # Check password security | |||
| weakness = password_weakness(data.password, data.username) | |||
| if weakness is not None: | |||
| return render.register(nav, register, weakness) | |||
| password_hash = bcrypt.hashpw(data.password.encode('UTF-8'), bcrypt.gensalt()) | |||
| models.register.set_user(data.username, password_hash, data.full_name, data.company, | |||
| data.email, data.street_address, data.city, data.state, | |||
| data.postal_code, data.country) | |||
| return render.register(nav, register_form, "User registered!") | |||
| @@ -78,3 +78,61 @@ def csrf_protected(f): | |||
| return f(*args, **kwargs) | |||
| return decorated | |||
| def is_common_password(password): | |||
| """Helper function that checks various common passwords.""" | |||
| def common_sequences(n): | |||
| # Check sequences of the same number | |||
| for i in range(n): | |||
| for j in range(n): | |||
| yield ''.join([str(i) for _ in range(j)]) | |||
| # Check incrementing sequences | |||
| for i in range(n): | |||
| # Starting at 0 | |||
| seq = ''.join([str(j) for j in range(i)]) | |||
| yield seq | |||
| # Starting at 1 | |||
| yield seq[1:] | |||
| # Decrementing | |||
| # Starting at 0 | |||
| yield seq[::-1] | |||
| # Starting at 1 | |||
| yield seq[1::-1] | |||
| common_passwords = [ | |||
| 'password', 'qwerty', 'iloveyou', '123123', 'abc123', 'admin', | |||
| 'passwrod', 'password1', 'beelance', 'beelance2' | |||
| ] | |||
| if password in common_passwords or password in common_sequences(12): | |||
| return True | |||
| return False | |||
| def password_weakness(password, username): | |||
| """ | |||
| Check if the password fulfills the password policy. | |||
| The policy is: | |||
| - At least 8 characters, but not more than 70 (due to bcrypt) | |||
| - Does not overlap with the username | |||
| - Not a common password | |||
| :param password: The password to check | |||
| :param username: The username of the user (used to check similarity) | |||
| :return: The most important weakness of the password, or None if it fulfills the policy | |||
| """ | |||
| if len(password) < 8: | |||
| return "The password must be at least 5 characters long." | |||
| elif len(password) > 70: | |||
| return "The password can't be longer than 70 characters." | |||
| elif password in username or username in password: | |||
| return "The password can't overlap with your username." | |||
| elif is_common_password(password): | |||
| return "The password is too common. Choose something more unique." | |||
| return None | |||