Skip to content

Commit 12ab078

Browse files
authored
Merge pull request #5 from thephilomaths/session-handling-and-user-controller
Session handling and user controller
2 parents a8d79e2 + bd35c23 commit 12ab078

File tree

12 files changed

+558
-28
lines changed

12 files changed

+558
-28
lines changed

.isort.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
line_length = 88
33
multi_line_output = 3
44
include_trailing_comma = True
5-
known_third_party = cryptography,flask,pytest,sqlalchemy
5+
known_third_party = config,cryptography,flask,pytest,sqlalchemy,src

requirements.txt

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,37 @@
11
appdirs==1.4.4
2-
cfgv==3.2.0
3-
click==7.1.2
4-
distlib==0.3.1
5-
filelock==3.0.12
6-
Flask==1.1.2
7-
identify==1.4.29
8-
itsdangerous==1.1.0
9-
Jinja2==2.11.2
10-
MarkupSafe==1.1.1
11-
nodeenv==1.5.0
12-
pre-commit==2.7.1
13-
PyYAML==5.3.1
14-
six==1.15.0
15-
SQLAlchemy==1.3.19
16-
toml==0.10.1
17-
virtualenv==20.0.31
18-
Werkzeug==1.0.1
19-
appdirs==1.4.4
202
attrs==20.1.0
3+
black==20.8b1
214
cfgv==3.2.0
225
click==7.1.2
236
distlib==0.3.1
247
filelock==3.0.12
8+
flake8==3.8.3
259
Flask==1.1.2
2610
identify==1.4.29
2711
iniconfig==1.0.1
2812
itsdangerous==1.1.0
2913
Jinja2==2.11.2
3014
MarkupSafe==1.1.1
15+
mccabe==0.6.1
3116
more-itertools==8.5.0
17+
mypy-extensions==0.4.3
3218
nodeenv==1.5.0
3319
packaging==20.4
20+
pathspec==0.8.0
3421
pluggy==0.13.1
3522
pre-commit==2.7.1
3623
psycopg2==2.8.5
3724
py==1.9.0
25+
pycodestyle==2.6.0
26+
pyflakes==2.2.0
3827
pyparsing==2.4.7
3928
pytest==6.0.1
4029
PyYAML==5.3.1
30+
regex==2020.9.27
4131
six==1.15.0
4232
SQLAlchemy==1.3.19
4333
toml==0.10.1
34+
typed-ast==1.4.1
35+
typing-extensions==3.7.4.3
4436
virtualenv==20.0.31
4537
Werkzeug==1.0.1

ssh_manager_backend/app/controllers/__init__.py

Whitespace-only changes.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import os
2+
3+
from cryptography.hazmat.backends import default_backend
4+
from cryptography.hazmat.primitives import hashes
5+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
6+
7+
from ssh_manager_backend.app.services.aes import AES
8+
9+
10+
"""
11+
ABBREVIATIONS:
12+
13+
kek = key encryption key
14+
dek = decryption key
15+
iv = initialization vector
16+
"""
17+
18+
19+
class Secrets:
20+
__csprnglength__ = 32
21+
__ivlength__ = 16
22+
23+
def __init__(self):
24+
self.kek = None
25+
self.dek = None
26+
self.salt_for_dek = None
27+
self.iv_for_dek = None
28+
self.salt_for_kek = None
29+
self.iv_for_kek = None
30+
self.salt_for_password = None
31+
32+
def set_secrets(self, secrets: dict):
33+
"""
34+
Sets the value for class attributes based on the input
35+
36+
Parameters
37+
----------
38+
secrets: dict
39+
The secrets dictionary used for setting the class attributes
40+
----------
41+
"""
42+
43+
self.dek = secrets["encryptedDek"]
44+
self.iv_for_dek = secrets["ivForDek"]
45+
self.salt_for_dek = secrets["saltForDek"]
46+
self.iv_for_kek = secrets["ivForKek"]
47+
self.salt_for_kek = secrets["saltForKek"]
48+
self.salt_for_password = secrets["saltForPassword"]
49+
50+
@staticmethod
51+
def generate_secure_random(length: int) -> bytes:
52+
"""
53+
Generated a cryptographically secure sequence of random bytes of the specified length
54+
55+
:param length:
56+
"""
57+
58+
secure_random = os.urandom(length)
59+
return secure_random
60+
61+
def generate_kek(self, password: str) -> bytes:
62+
"""
63+
Generated key encryption key from the password using PBKDF2 (Password based key definition function)
64+
65+
:param password:
66+
"""
67+
68+
kdf = PBKDF2HMAC(
69+
algorithm=hashes.SHA256(),
70+
length=32,
71+
salt=self.salt_for_kek,
72+
iterations=100000,
73+
backend=default_backend(),
74+
)
75+
76+
return kdf.derive(bytes(password, encoding="utf-8"))
77+
78+
def encrypt_dek(self) -> bytes:
79+
"""
80+
Encrypts the decryption key from the user's password/kek
81+
"""
82+
83+
aes = AES(self.kek, self.iv_for_kek)
84+
return aes.encrypt(self.dek)
85+
86+
def generate_secrets(self, password: bytes):
87+
"""
88+
A utility function which calls the above functions for generating the value of secrets. This function is
89+
called during the registration phase.
90+
91+
:param password:
92+
"""
93+
94+
self.salt_for_password = self.generate_secure_random(self.__csprnglength__)
95+
self.dek = self.generate_secure_random(self.__csprnglength__)
96+
self.salt_for_dek = self.generate_secure_random(self.__csprnglength__)
97+
self.iv_for_dek = self.generate_secure_random(self.__ivlength__)
98+
self.salt_for_kek = self.generate_secure_random(self.__csprnglength__)
99+
self.iv_for_kek = self.generate_secure_random(self.__ivlength__)
100+
self.kek = self.generate_kek(password)
101+
self.dek = self.encrypt_dek()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import uuid
2+
from typing import Tuple, Union
3+
4+
from src.modules.utils import hash_data
5+
6+
from ssh_manager_backend.app.controllers.secrets import Secrets
7+
from ssh_manager_backend.app.models import SessionModel, UserModel
8+
9+
10+
class User:
11+
def __init__(self):
12+
self.secrets: Secrets = Secrets()
13+
self.username: str = ""
14+
self.__access_token: str = ""
15+
self.__password: str = ""
16+
self.email: str = ""
17+
self.name: str = ""
18+
self.admin: str = ""
19+
self.user: UserModel = UserModel()
20+
21+
def set_attributes(
22+
self, username: str, password: str, email: str, name: str, access_token: str
23+
):
24+
"""
25+
Sets the class attribute based on the function arguments.
26+
27+
:param username: The username of the user
28+
:param password: The password of the user
29+
:param email: The email of the user
30+
:param name: The name of the user
31+
:param access_token: The access token of the user
32+
:return:
33+
"""
34+
35+
self.username = username
36+
self.__password = password
37+
self.email = email
38+
self.name = name
39+
self.__access_token = access_token
40+
41+
def register(self, username: str, password: str, email: str, name: str) -> bool:
42+
"""
43+
Generates the secrets of the user based on the password. Hashes the password using SAH256 and stores the
44+
generated information in DynamoDB.
45+
46+
:param username:
47+
:param password:
48+
:param email:
49+
:param name:
50+
:param table_name:
51+
:return: True/False for success/failure
52+
"""
53+
54+
self.set_attributes(
55+
username=username,
56+
password=password,
57+
email=email,
58+
name=name,
59+
access_token=uuid.uuid4().hex,
60+
)
61+
62+
self.secrets.generate_secrets(password=self.__password)
63+
self.__password = hash_data(self.__password, self.salt_for_password)
64+
user_is_admin = True
65+
66+
if self.user.admin_exists():
67+
user_is_admin = False
68+
69+
return self.user.create(
70+
name=name,
71+
username=self.username,
72+
password=self.__password,
73+
admin=user_is_admin,
74+
encrypted_dek=self.secrets.encrypted_dek,
75+
iv_for_dek=self.secrets.iv_for_dek,
76+
salt_for_dek=self.secrets.salt_for_dek,
77+
iv_for_kek=self.secrets.iv_for_kek,
78+
salt_for_kek=self.secrets.salt_for_kek,
79+
salt_for_password=self.secrets.salt_for_password,
80+
)
81+
82+
def login(self, username: str, password: str) -> Tuple[Union[bool, str]]:
83+
"""
84+
Handles user login.
85+
86+
:param username: The username of the user
87+
:param password: The password of the user
88+
:param table_name: The table in which the data is to be inserted
89+
:return: access token upon successful login
90+
"""
91+
92+
if self.user.exists(username):
93+
if self.user.password_match(
94+
username=username, password=hash_data(password)
95+
):
96+
access_token: str = uuid.uuid4()
97+
session: SessionModel = SessionModel()
98+
if not session.exists(username=username):
99+
session.create(usernmae=username, access_token=access_token)
100+
else:
101+
session.activate_session(username=username)
102+
103+
return (True, access_token)
104+
else:
105+
return (False, "Password does not match")
106+
107+
return (False, "User does not exists")
108+
109+
def is_admin(self) -> bool:
110+
return self.user.get_user(username=self.username).admin
111+
112+
@property
113+
def password(self) -> str:
114+
"""
115+
Returns the password of the user
116+
"""
117+
118+
return self.__password
119+
120+
@property
121+
def access_token(self) -> str:
122+
"""
123+
Returns the access token of the user
124+
"""
125+
126+
return self.__access_token
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from ssh_manager_backend.app.models.access_control import AccessControl
2-
from ssh_manager_backend.app.models.keys import Key
3-
from ssh_manager_backend.app.models.keys_mapping import KeyMapping
4-
from ssh_manager_backend.app.models.user import User
1+
from ssh_manager_backend.app.models.access_control import AccessControlModel
2+
from ssh_manager_backend.app.models.keys import KeyModel
3+
from ssh_manager_backend.app.models.keys_mapping import KeyMappingModel
4+
from ssh_manager_backend.app.models.session import SessionModel
5+
from ssh_manager_backend.app.models.user import UserModel

0 commit comments

Comments
 (0)