- Prototype Summary: User Authentication and Access Control System
This document provides a comprehensive summary of the prototype user authentication and access control system developed for Finest Holdings.
- Model: Role-Based Access Control (RBAC)
- Justification: RBAC's effectiveness in aligning with organizational roles and permissions made it the ideal choice.
- Representation: Access Control List (ACL)
- Justification: ACL provides a manageable and clear mapping of roles to permissions.
- Roles and Permissions: Defined roles (e.g., 'Regular Client', 'Premium Client') with specific permissions (e.g., ' view_account_balance').
- System Implementation: Implemented in Python, ensuring user roles align with permissions for actions.
The best way to view the system in action is to examine the tests. Particularly test_access_control.py
Initially, The database is injected with users. These users are the same from the sample list provided by Finvest Holdings.
@classmethod
def setUpClass(cls):
# print(f"Creating test database at: {cls.test_db_path}")
create_database(cls.test_db_path)
add_user(cls.test_db_path, "Mischa Lowery", "password123", "Regular Client")
add_user(cls.test_db_path, "Veronica Perez", "password123", "Regular Client")
add_user(cls.test_db_path, "Willow Garza", "password123", "Premium Client")
add_user(cls.test_db_path, "Nala Preston", "password123", "Premium Client")
add_user(cls.test_db_path, "Winston Callahan", "password123", "Teller")
add_user(cls.test_db_path, "Kelan Gough", "password123", "Teller")
add_user(cls.test_db_path, "Nelson Wilkins", "password123", "Financial Advisor")
add_user(cls.test_db_path, "Kelsie Chang", "password123", "Financial Advisor")
add_user(cls.test_db_path, "Howard Linkler", "password123", "Compliance Officer")
add_user(cls.test_db_path, "Stefania Smart", "password123", "Compliance Officer")
add_user(cls.test_db_path, "Kodi Matthews", "password123", "Financial Planner")
add_user(cls.test_db_path, "Malikah Wu", "password123", "Financial Planner")
add_user(cls.test_db_path, "Stacy Kent", "password123", "Investment Analyst")
add_user(cls.test_db_path, "Keikilana Kapahu", "password123", "Investment Analyst")
add_user(cls.test_db_path, "Caroline Lopez", "password123", "Technical Support")
add_user(cls.test_db_path, "Pawel Barclay", "password123", "Technical Support")
Within roles.py
we can examine the roles, permissions, and acl structure for the project
roles: dict[str, Role] = {
"Regular Client": Role(
"Regular Client",
[
"view_account_balance",
"view_investments_portfolio",
"view_financial_advisor_contact",
],
),
"Premium Client": Role(
"Premium Client",
[
"view_account_balance",
"view_investments_portfolio",
"modify_investment_portfolio",
"view_financial_advisor_contact",
"view_investment_analyst_contact",
],
),
"Teller": Role(
"Teller",
[
"view_client_balance",
"view_client_investment_portfolio",
"access_system_during_business_hours",
],
),
"Financial Advisor": Role(
"Financial Advisor",
[
"view_client_balance",
"view_client_investment_portfolio",
"modify_client_investment_portfolio",
"view_private_consumer_instruments",
],
),
"Compliance Officer": Role(
"Compliance Officer",
[
"view_client_balance",
"view_client_investment_portfolio",
"validate_investment_portfolio_modifications",
],
),
"Financial Planner": Role(
"Financial Planner",
[
"view_client_balance",
"view_client_investment_portfolio",
"modify_client_investment_portfolio",
"view_money_market_instruments",
"view_private_consumer_instruments",
],
),
"Investment Analyst": Role(
"Investment Analyst",
[
"view_client_balance",
"view_client_investment_portfolio",
"view_derivatives_trading",
"modify_client_investment_portfolio",
"view_money_market_instruments",
"view_interest_instruments",
"view_private_consumer_instruments",
],
),
"Technical Support": Role(
"Technical Support",
[
"view_client_balance",
"view_client_investment_portfolio",
"view_client_information",
"request_client_account_access",
],
),
}
acl = {
"account_balance": {
"Regular Client": ["view_account_balance"],
"Premium Client": ["view_account_balance"],
"Teller": ["view_client_balance"],
"Financial Advisor": ["view_client_balance"],
"Compliance Officer": ["view_client_balance"],
"Financial Planner": ["view_client_balance"],
"Investment Analyst": ["view_client_balance"],
"Technical Support": ["view_client_balance"],
},
"investments_portfolio": {
"Regular Client": ["view_investments_portfolio"],
"Premium Client": ["view_investments_portfolio", "modify_investment_portfolio"],
"Teller": ["view_client_investment_portfolio"],
"Financial Advisor": ["view_client_investment_portfolio", "modify_client_investment_portfolio"],
"Compliance Officer": ["view_client_investment_portfolio"],
"Financial Planner": ["view_client_investment_portfolio", "modify_client_investment_portfolio"],
"Investment Analyst": ["view_client_investment_portfolio", "modify_client_investment_portfolio"],
"Technical Support": ["view_client_investment_portfolio"],
},
"financial_advisor_contact": {
"Regular Client": ["view_financial_advisor_contact"],
"Premium Client": ["view_financial_advisor_contact"],
"Technical Support": ["view_client_information"],
},
"investment_analyst_contact": {
"Premium Client": ["view_investment_analyst_contact"],
"Technical Support": ["view_client_information"],
},
"private_consumer_instruments": {
"Financial Advisor": ["view_private_consumer_instruments"],
"Financial Planner": ["view_private_consumer_instruments"],
"Investment Analyst": ["view_private_consumer_instruments"],
},
"money_market_instruments": {
"Financial Planner": ["view_money_market_instruments"],
"Investment Analyst": ["view_money_market_instruments"],
},
"interest_instruments": {
"Investment Analyst": ["view_interest_instruments"],
},
"derivatives_trading": {
"Investment Analyst": ["view_derivatives_trading"],
},
"access_system_during_business_hours": {
"Teller": ["access_system_during_business_hours"],
},
"validate_investment_portfolio_modifications": {
"Compliance Officer": ["validate_investment_portfolio_modifications"],
},
"request_client_account_access": {
"Technical Support": ["request_client_account_access"],
},
"view_client_information": {
"Technical Support": ["view_client_information"],
},
}
We can immediately see the benefits of this structure as changes in permissions or addition of roles is quite easy.
Note: This was generated from the create_access_matrix_df
method within roles.py
but please be aware that the naming of the method is misleading. It still provides a good visual graphic though!
Roles/Resources | access_system_during_business_hours | account_balance | derivatives_trading | financial_advisor_contact | interest_instruments | investment_analyst_contact | investments_portfolio | money_market_instruments | private_consumer_instruments | request_client_account_access | validate_investment_portfolio_modifications | view_client_information |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Regular Client | view_account_balance | view_financial_advisor_contact | view_investments_portfolio | |||||||||
Premium Client | view_account_balance | view_financial_advisor_contact | view_investment_analyst_contact | view_investments_portfolio, modify_investment_portfolio | ||||||||
Teller | access_system_during_business_hours | view_client_balance | view_client_investment_portfolio | |||||||||
Financial Advisor | view_client_balance | view_client_investment_portfolio, modify_client_investment_portfolio | view_private_consumer_instruments | |||||||||
Compliance Officer | view_client_balance | view_client_investment_portfolio | validate_investment_portfolio_modifications | |||||||||
Financial Planner | view_client_balance | view_client_investment_portfolio, modify_client_investment_portfolio | view_money_market_instruments | view_private_consumer_instruments | ||||||||
Investment Analyst | view_client_balance | view_derivatives_trading | view_interest_instruments | view_client_investment_portfolio, modify_client_investment_portfolio | view_money_market_instruments | view_private_consumer_instruments | ||||||
Technical Support | view_client_balance | view_client_information | view_client_information | view_client_investment_portfolio | request_client_account_access | view_client_information |
- Method: Python's
unittest
framework. - Please view and run
test_access_control.py
to see the system in a simulated test.
- Algorithm: bcrypt for hashing passwords.
- Justification: bcrypt's salted hashes and security features are ideal for preventing brute-force attacks.
- Structure: SQLite database records with username, hashed password, salt, and user role.
- Following same procedure described in Figure 2 of the assignment document.
- Selected SQLite over the suggested
passwd.txt
file as it offers better features and is still in plaintext
- Technology: Python and SQLite.
- Functionality: Secure storage and retrieval of hashed passwords.
Please examine database.py
to see how the system uses BCrypt to store the hash and the salt in the sqlite database.
Here is a sample where we can see how a salt is added to the user object before being added to the database
def add_user(db_path, username, password, role):
conn = sqlite3.connect(db_path)
c = conn.cursor()
# Check if the user already exists
c.execute("SELECT * FROM users WHERE username = ?", (username,))
if c.fetchone():
print(f"User '{username}' already exists. Skipping.")
pass
else:
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode(), salt)
c.execute('''
INSERT INTO users (username, hashed_password, salt, role)
VALUES (?, ?, ?, ?)
''', (username, hashed_password.decode(), salt.decode(), role))
conn.commit()
conn.close()
- Focus: Security of password storage and integrity of the hashing mechanism.
- Interface: Command-line interface for inputting username, password, and role.
- Followed the same design as the one proposed in the assignment document
- Functionality: Enforces password policies and checks against common password lists.
- Integration: Seamlessly adds new users to the database after password validation.
I used this list of common passwords provided by SecLists.
All the passwords within that list of common passwords are forbidden as passwords for any user. The system also implements the following:
- Passwords must have a minimum length of 8
- Passwords must include:
- 1 upper-case letter;
- 1 lower-case letter;
- one number;
- one special character (!@#$%^&*);
- Passwords cannot match the format of license plates, postal codes, telephone numbers, and calendar dates.
- Finally, the username cannot be within the password. This test is a good example of how this system
We can see the implementation within password_checker.py
It is even clearer to examine the tests in test_password_checker.py
. We can see in there that all these cases are tested for:
# test_password_checker.py
import unittest
from src.authentication.password_checker import is_password_valid
class TestPasswordChecker(unittest.TestCase):
def test_valid_password(self):
self.assertTrue(is_password_valid("Soccer123!", "user"))
def test_password_length_short(self):
self.assertFalse(is_password_valid("Short1!", "user"))
def test_no_number(self):
self.assertFalse(is_password_valid("NoNumber!", "user"))
def test_user_id_match(self):
self.assertFalse(is_password_valid("user123!", "user"))
def test_no_special_char(self):
self.assertFalse(is_password_valid("NoSpecial0", "user"))
def test_no_uppercase(self):
self.assertFalse(is_password_valid("nouppercase1!", "user"))
def test_no_lowercase(self):
self.assertFalse(is_password_valid("NOLOWERCASE1!", "user"))
def test_common_password(self):
self.assertFalse(is_password_valid("Password123!", "user"))
def test_date_format_password(self):
self.assertFalse(is_password_valid("01/01/2000", "user"))
if __name__ == "__main__":
unittest.main()
- Scenarios: Includes weak password rejection and successful user addition.
- Interface: Command-line interface for user login.
Finvest Holdings
Client Holdings and Information System
---------------------------------------------------
1. Sign Up
2. Sign In
3. Exit
Choose an option: 1
Enrolment Process
Enter username: kareemery
Enter password: SecurePassword123!
Enter role: Teller
USER ENROLMENT SUCCESSFUL.
Finvest Holdings
Client Holdings and Information System
---------------------------------------------------
1. Sign Up
2. Sign In
3. Exit
Choose an option: 2
Login Process
Enter username: kareemery
Enter password: SecurePassword123!
ACCESS GRANTED
User Information:
User ID: kareemery
Role: Teller
Permissions: view_client_balance, view_client_investment_portfolio, access_system_during_business_hours
Options:
1. Logout
2. Exit
Choose an action: 2
Process finished with exit code 0
The implementation for this can be found in main.py
:
from src.enrolment.enrolment import enrol_user, user_login
from src.authentication.database import create_database
from src.access_control.control import can_perform_action
def main():
create_database()
while True:
print("\nFinvest Holdings")
print("Client Holdings and Information System")
print("---------------------------------------------------")
print("1. Sign Up")
print("2. Sign In")
print("3. Exit")
choice = input("Choose an option: ")
if choice == "1":
enrol_user("finvest.db")
elif choice == "2":
user = user_login("finvest.db")
if user:
print(f"\nUser Information:")
print(f"User ID: {user.name}")
print(f"Role: {user.role.name}")
print(f"Permissions: {', '.join(user.role.permissions)}")
while True:
print("\nOptions:")
print("1. Logout")
print("2. Exit")
action_choice = input("Choose an action: ")
if action_choice == "1":
break
elif action_choice == "2":
exit()
else:
print("Invalid option.")
else:
print("Invalid login credentials.")
elif choice == "3":
break
else:
print("Invalid choice. Please try again.")
if __name__ == "__main__":
main()
- Display: Shows user ID, role, and permissions post-login.
- My approach uses
@patch
fromunittest.mock
to simulate the response from the user expected from input(). This way we can more quickly simulate the interaction. - I was able to test the creation of all the different types of roles needed. Please see
test_access_control.py
andtest_enrolment.py
to see it personally. - Here is a snippet showing some important tests:
class TestAccessControl(unittest.TestCase):
test_db_path = "test_finvest.db"
@classmethod
def setUpClass(cls):
# print(f"Creating test database at: {cls.test_db_path}")
create_database(cls.test_db_path)
add_user(cls.test_db_path, "Mischa Lowery", "Password123!", "Regular Client")
add_user(cls.test_db_path, "Veronica Perez", "Password123!", "Regular Client")
add_user(cls.test_db_path, "Willow Garza", "Password123!", "Premium Client")
add_user(cls.test_db_path, "Nala Preston", "Password123!", "Premium Client")
add_user(cls.test_db_path, "Winston Callahan", "Password123!", "Teller")
add_user(cls.test_db_path, "Kelan Gough", "Password123!", "Teller")
add_user(cls.test_db_path, "Nelson Wilkins", "Password123!", "Financial Advisor")
add_user(cls.test_db_path, "Kelsie Chang", "Password123!", "Financial Advisor")
add_user(cls.test_db_path, "Howard Linkler", "Password123!", "Compliance Officer")
add_user(cls.test_db_path, "Stefania Smart", "Password123!", "Compliance Officer")
add_user(cls.test_db_path, "Kodi Matthews", "Password123!", "Financial Planner")
add_user(cls.test_db_path, "Malikah Wu", "Password123!", "Financial Planner")
add_user(cls.test_db_path, "Stacy Kent", "Password123!", "Investment Analyst")
add_user(cls.test_db_path, "Keikilana Kapahu", "Password123!", "Investment Analyst")
add_user(cls.test_db_path, "Caroline Lopez", "Password123!", "Technical Support")
add_user(cls.test_db_path, "Pawel Barclay", "Password123!", "Technical Support")
@classmethod
def tearDownClass(cls):
os.remove("test_finvest.db")
# Database-related tests
def test_add_and_get_user(self):
username = "testuser"
password = "testpassword"
role = "Regular Client"
add_user(self.test_db_path, username, password, role)
user = get_user(self.test_db_path, username)
self.assertIsNotNone(user)
self.assertEqual(user[0], username)
# self.assertTrue(bcrypt.checkpw(password.encode(), user[1].encode()))
self.assertEqual(user[3], role)
# Authentication and access control tests
def test_user_authentication(self):
user = authenticate_user("Nala Preston", "Password123!", self.test_db_path)
self.assertIsNotNone(user)
def test_invalid_authentication(self):
user = authenticate_user("Nala Preston", "wrong_password", self.test_db_path)
self.assertIsNone(user)
# test each role
def test_regular_client_permissions(self):
user = authenticate_user("Mischa Lowery", "Password123!", self.test_db_path)
self.assertTrue(can_perform_action(user, "view_account_balance", "account_balance"))
self.assertFalse(can_perform_action(user, "modify_investment_portfolio", "investments_portfolio"))
def test_premium_client_permissions(self):
user = authenticate_user("Willow Garza", "Password123!", self.test_db_path)
self.assertTrue(can_perform_action(user, "view_investments_portfolio", "investments_portfolio"))
self.assertTrue(can_perform_action(user, "modify_investment_portfolio", "investments_portfolio"))
def test_unauthorized_access(self):
user = authenticate_user("UnauthorizedUser", "password123", self.test_db_path)
self.assertIsNone(user)
def test_invalid_role(self):
with self.assertRaises(ValueError):
User("InvalidRoleUser", "NonExistingRole")
As you can see, users from the given table are added to the database, getting and setting are both tested, invalid edge cases are tests, and 3 roles are authenticated and verified that they return the info they should
- Setup: Requires Python 3.x.
- Virtual Environment: Run
python -m venv venv
and make sure that your terminal prints (venv) at the end of every execution. - Dependencies: Run
pip install -r requirements.txt
- Execution: Run
python main.py
in the project directory. - Operation: Follow the prompts for user enrollment or login.
- Tests: Run
test_access_control.py
,test_enrolment.py
, andtest_password_checker.py
- Setup: Requires Python 3.x.
- Update VM: Run the following commands:
sudo apt-get update
sudo apt-get upgrade
- Verify that python is up-to-date:
python3 --version
- Virtual Environment: Run
python3 -m venv venv
and make sure that your terminal prints (venv) at the end of every execution. - Dependencies: Run
python3 -m pip install -r requirements.txt
- Execution: Run
python3 src/main.py
in the project directory. - Operation: Follow the prompts for user enrollment or login.
- Tests: Run
test_access_control.py
,test_enrolment.py
, andtest_password_checker.py
-
Enrollment Process:
- Choose 'Sign Up'.
- Input username, password, and role.
- Confirmation upon successful enrollment.
-
Login Process:
- Choose 'Sign In'.
- Enter username and password.
- Post-login, user details and permissions are displayed.
Finvest Holdings
Client Holdings and Information System
---------------------------------------------------
1. Sign Up
2. Sign In
3. Exit
Choose an option: 1
Enrolment Process
Enter username: kareemery
Enter password: SecurePassword123!
Enter role: Teller
USER ENROLMENT SUCCESSFUL.
Finvest Holdings
Client Holdings and Information System
---------------------------------------------------
1. Sign Up
2. Sign In
3. Exit
Choose an option: 2
Login Process
Enter username: kareemery
Enter password: SecurePassword123!
ACCESS GRANTED
User Information:
User ID: kareemery
Role: Teller
Permissions: view_client_balance, view_client_investment_portfolio, access_system_during_business_hours
Options:
1. Logout
2. Exit
Choose an action: 2
Process finished with exit code 0
Document prepared for Finvest Holdings to evaluate the security and functionality of the prototype system.