Skip to content

Commit 5442ac4

Browse files
committed
Session handling and user controller
1 parent a8d79e2 commit 5442ac4

File tree

12 files changed

+578
-10
lines changed

12 files changed

+578
-10
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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,34 @@ six==1.15.0
4242
SQLAlchemy==1.3.19
4343
toml==0.10.1
4444
virtualenv==20.0.31
45+
Werkzeug==1.0.1appdirs==1.4.4
46+
attrs==20.1.0
47+
cfgv==3.2.0
48+
click==7.1.2
49+
distlib==0.3.1
50+
filelock==3.0.12
51+
flake8==3.8.3
52+
Flask==1.1.2
53+
identify==1.4.29
54+
iniconfig==1.0.1
55+
itsdangerous==1.1.0
56+
Jinja2==2.11.2
57+
MarkupSafe==1.1.1
58+
mccabe==0.6.1
59+
more-itertools==8.5.0
60+
nodeenv==1.5.0
61+
packaging==20.4
62+
pluggy==0.13.1
63+
pre-commit==2.7.1
64+
psycopg2==2.8.5
65+
py==1.9.0
66+
pycodestyle==2.6.0
67+
pyflakes==2.2.0
68+
pyparsing==2.4.7
69+
pytest==6.0.1
70+
PyYAML==5.3.1
71+
six==1.15.0
72+
SQLAlchemy==1.3.19
73+
toml==0.10.1
74+
virtualenv==20.0.31
4575
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
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from typing import Union
2+
3+
from sqlalchemy.exc import SQLAlchemyError
4+
5+
from ssh_manager_backend.db import User, UserSession
6+
from ssh_manager_backend.db.database import db_session
7+
8+
9+
class SessionModel:
10+
def __init__(self):
11+
self.session = db_session()
12+
13+
def exists(self, username: str) -> bool:
14+
"""
15+
Checks whether a session exists.
16+
17+
:params username: The username of the user.
18+
:return:
19+
"""
20+
21+
return (
22+
self.session.query(UserSession)
23+
.join(User)
24+
.filter(User.username == username)
25+
.first()
26+
is not None
27+
)
28+
29+
def create(self, username: str, access_token: str) -> bool:
30+
"""
31+
Creates a user session and returns access token upon success.
32+
:param username: The username of the user,
33+
:return:
34+
"""
35+
36+
try:
37+
user_session: UserSession = UserSession(
38+
access_token=access_token, username=username, active=True
39+
)
40+
self.session.add(user_session)
41+
self.session.commit()
42+
except SQLAlchemyError:
43+
self.session.rollback()
44+
return False
45+
except AttributeError:
46+
return False
47+
48+
return True
49+
50+
def activate_session(self, username: str) -> bool:
51+
"""
52+
Activates a user session.
53+
:param username: The username of the user,
54+
:return:
55+
"""
56+
57+
try:
58+
self.session.query(UserSession).filter(
59+
UserSession.username == username
60+
).update({"active": True})
61+
self.session.commit()
62+
63+
return True
64+
except AttributeError:
65+
return False
66+
67+
def deactivate_session(self, username: str) -> bool:
68+
"""
69+
Activates a user session.
70+
:param username: The username of the user,
71+
:return:
72+
"""
73+
74+
try:
75+
self.session.query(UserSession).filter(
76+
UserSession.username == username
77+
).update({"active": False})
78+
self.session.commit()
79+
return True
80+
except AttributeError:
81+
return False
82+
83+
def access_token(self, username: str) -> Union[bool, str]:
84+
"""
85+
Returns access token of user.
86+
:param username: The usernameof the user,
87+
:return: access token
88+
"""
89+
90+
try:
91+
access_token: str = (
92+
self.session.query(UserSession)
93+
.filter(UserSession.username == username)
94+
.first()
95+
.access_token
96+
)
97+
return access_token
98+
except AttributeError:
99+
return False

0 commit comments

Comments
 (0)