From c1a624010cc2eda7802559c155be2f1786af37d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Gon=C3=A7alves=20Monnet?= Date: Thu, 6 Jun 2024 14:45:51 +0200 Subject: [PATCH 1/5] Update server.md --- Docs/server.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Docs/server.md b/Docs/server.md index 231ec66..c47aff3 100644 --- a/Docs/server.md +++ b/Docs/server.md @@ -8,6 +8,7 @@ - [3. Clone the Repository](./server.md/#3-clone-the-repository) - [4. Create the .env File](./server.md/#4-create-the-env-file) - [5. Build and Run the Docker Container](./server.md/#5-build-and-run-the-docker-container) + # The Active Directory part ## 1. Modify the LDAP Schema @@ -77,9 +78,9 @@ Then navigate into the server folder ```bash cd ./RD-AD/Server ``` -## 4. Create the .env File +## 4. Create the `.env` File -Create a .env file in the [server directory](../Server/) with the following content: +Create a `.env` file in the [server directory](../Server/) with the following content: ``` LDAPUSER=[The user you have created earlier] From 9b8588f7eb35eaf41cd0526e726b106c661df949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Gon=C3=A7alves=20Monnet?= Date: Fri, 7 Jun 2024 11:25:02 +0200 Subject: [PATCH 2/5] Add docstrings --- Server/Program/Webserver.py | 15 ++- Server/Program/database.py | 180 +++++++++++++++++++++++++++++++++--- Server/Program/ldapSync.py | 113 ++++++++++++++++++---- Server/Program/server.py | 3 +- 4 files changed, 278 insertions(+), 33 deletions(-) diff --git a/Server/Program/Webserver.py b/Server/Program/Webserver.py index 67e17aa..2bfa601 100644 --- a/Server/Program/Webserver.py +++ b/Server/Program/Webserver.py @@ -118,7 +118,6 @@ def door_access(): access_granted, upn = check_access(rfid_uid, door_id) if access_granted: - print("") log_access_attempt(DBFILE, upn, rfid_uid, True, door_id) return jsonify({"access_granted": True, "upn": upn}), 200 @@ -128,10 +127,24 @@ def door_access(): def run_flask_app(): + """ + Run the Flask web application. + + This function starts the Flask web application with debugging enabled, + no reloader, on the specified port and host. It serves as the main entry + point for running the web server. + """ app.run(debug=True, use_reloader=False, port=WebServerPORT, host="0.0.0.0") def run_webServer_thread(): + """ + Start the Flask web server in a separate thread. + + This function initializes and starts a new thread to run the Flask web + application. It allows the web server to run concurrently with other + tasks in the main program, ensuring the web interface remains responsive. + """ print(f"STARTING WEB SERVER ON PORT {WebServerPORT}") flask_thread = Thread(target=run_flask_app, daemon=True) flask_thread.start() diff --git a/Server/Program/database.py b/Server/Program/database.py index c5180d3..96c72e2 100644 --- a/Server/Program/database.py +++ b/Server/Program/database.py @@ -5,6 +5,18 @@ from env import DBFILE # Function to check if a table exists in the database def table_exists(cursor, table_name): + """ + Check if a table exists in the database. + + This function checks whether a table with the specified name exists in the database. + + ## Parameters: + - cursor (sqlite3.Cursor): The cursor object to execute SQL queries. + - table_name (str): The name of the table to check. + + ## Returns: + - bool: True if the table exists, False otherwise. + """ cursor.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,) ) @@ -13,6 +25,14 @@ def table_exists(cursor, table_name): # Function to create the Users table def create_users_table(cursor): + """ + Create the Users table in the database. + + This function creates the Users table with columns for user principal name (upn), RFID UID, and member of groups. + + ## Parameters: + - cursor (sqlite3.Cursor): The cursor object to execute SQL queries. + """ cursor.execute("""CREATE TABLE Users ( upn TEXT PRIMARY KEY, rFIDUID TEXT, @@ -23,6 +43,14 @@ def create_users_table(cursor): # Function to create the Groups table def create_groups_table(cursor): + """ + Create the Groups table in the database. + + This function creates the Groups table with a single column for common name (cn) of the group. + + ## Parameters: + - cursor (sqlite3.Cursor): The cursor object to execute SQL queries. + """ cursor.execute("""CREATE TABLE Groups ( cn TEXT PRIMARY KEY )""") @@ -30,6 +58,14 @@ def create_groups_table(cursor): # Function to create the Doors table def create_doors_table(cursor): + """ + Create the Doors table in the database. + + This function creates the Doors table with columns for door ID and associated group common name. + + ## Parameters: + - cursor (sqlite3.Cursor): The cursor object to execute SQL queries. + """ cursor.execute("""CREATE TABLE Doors ( id INTEGER PRIMARY KEY, GroupCn TEXT, @@ -40,9 +76,13 @@ def create_doors_table(cursor): # Function to create the logs table def create_logs_table(cursor): """ - Create a log table with columns id, timestamp, user, and granted. + Create the logs table in the database. - :param db_file: The database file path. + This function creates the logs table with columns for ID (auto-incremented), timestamp, user, RFID UID, door ID, + and access granted status. Foreign key constraints are set on the door ID, user, and RFID UID columns. + + ## Parameters: + - cursor (sqlite3.Cursor): The cursor object to execute SQL queries. """ cursor.execute(""" CREATE TABLE IF NOT EXISTS log ( @@ -61,6 +101,19 @@ def create_logs_table(cursor): # Function to setup the database def setup_database(db_file): + """ + Set up the SQLite database by creating necessary tables if they don't already exist. + + This function checks if the Users, Groups, Doors, and Log tables exist in the database. If any of them don't exist, + it creates them using their respective creation functions. After creating or verifying the tables, it commits + the changes and closes the database connection. + + ## Parameters: + - db_file (str): The file path to the SQLite database. + + ## Returns: + - None + """ # Connect to the SQLite database conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -98,13 +151,20 @@ def setup_database(db_file): def log_access_attempt(db_file, user, rFIDUID, granted, doorID): """ - Log an access attempt to the log table. + Log an access attempt to the database. - :param db_file: The database file path. - :param user: The user attempting access. - :param rFIDUID: The user's tag uid - :param granted: Whether access was granted (True/False). - :param doorID: The door id + This function inserts a new entry into the log table of the SQLite database, recording details about the access attempt, + such as the timestamp, user, RFID UID, whether access was granted, and the door ID. + + ## Parameters: + - db_file (str): The file path to the SQLite database. + - user (str): The user's UPN (User Principal Name). + - rFIDUID (str): The RFID UID associated with the access attempt. + - granted (bool): A boolean indicating whether access was granted (True) or denied (False). + - doorID (int): The ID of the door where the access attempt occurred. + + # Returns: + - None """ conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -122,6 +182,12 @@ def log_access_attempt(db_file, user, rFIDUID, granted, doorID): def print_users_table(cursor): + """ + Print the content of the Users table. + + ## Parameters: + - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. + """ cursor.execute("SELECT * FROM Users") rows = cursor.fetchall() print("Users:") @@ -131,6 +197,12 @@ def print_users_table(cursor): # Function to print the content of the Groups table def print_groups_table(cursor): + """ + Print the content of the Groups table. + + ## Parameters: + - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. + """ cursor.execute("SELECT * FROM Groups") rows = cursor.fetchall() print("Groups:") @@ -140,6 +212,12 @@ def print_groups_table(cursor): # Function to print the content of the Doors table def print_doors_table(cursor): + """ + Print the content of the Doors table. + + ## Parameters: + - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. + """ cursor.execute("SELECT * FROM Doors") rows = cursor.fetchall() print("Doors:") @@ -149,6 +227,12 @@ def print_doors_table(cursor): # Function to print the content of the Log table def print_log_table(cursor): + """ + Print the content of the Log table. + + ## Parameters: + - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. + """ cursor.execute("SELECT * FROM log") rows = cursor.fetchall() print("Logs:") @@ -158,6 +242,12 @@ def print_log_table(cursor): # Function to print the content of the entire database def print_database_content(db_file): + """ + Print the content of the entire database. + + ## Parameters: + - db_file (str): The file path to the SQLite database. + """ conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -172,7 +262,9 @@ def print_database_content(db_file): def get_logs(): """ Fetch all logs from the log table in the database. - :return: List of log records. + + ## Returns: + - list: List of log records. """ conn = sqlite3.connect(DBFILE) cursor = conn.cursor() @@ -193,8 +285,12 @@ def get_latest_logs(db_file, limit=10): """ Fetch the latest logs from the database. - :param limit: The number of latest logs to fetch. - :return: List of log entries. + ## Parameters: + - db_file (str): The file path to the SQLite database. + - limit (int): The number of latest logs to fetch. Default is 10. + + ## Returns: + - list: List of log entries. """ conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -216,6 +312,15 @@ def get_latest_logs(db_file, limit=10): # Function to fetch list of existing groups from the database def get_existing_groups(db_file): + """ + Fetches a list of existing groups from the database. + + ## Parameters: + - db_file (str): The file path to the SQLite database. + + ## Returns: + - list: List of existing group names. + """ try: conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -229,6 +334,15 @@ def get_existing_groups(db_file): def delete_group_from_database(group_cn): + """ + Delete a group from the database. + + This function deletes a group with the specified common name (cn) from both the Groups and Doors tables + in the database. + + ## Parameters: + - group_cn (str): The common name of the group to delete. + """ conn = sqlite3.connect(DBFILE) cursor = conn.cursor() cursor.execute("DELETE FROM Groups WHERE cn = ?", (group_cn,)) @@ -238,6 +352,14 @@ def delete_group_from_database(group_cn): def get_doors(): + """ + Retrieve all doors from the database. + + This function fetches all rows from the Doors table in the database and returns them as a list of tuples. + + ## Returns: + - list: A list of tuples representing door records. + """ conn = sqlite3.connect(DBFILE) cursor = conn.cursor() cursor.execute("SELECT * FROM Doors") @@ -249,7 +371,9 @@ def get_doors(): def get_users(): """ Fetch all users from the Users table in the database. - :return: List of user records. + + ## Returns: + - list: List of user records. """ conn = sqlite3.connect(DBFILE) cursor = conn.cursor() @@ -263,6 +387,22 @@ def get_users(): # Function to add a door to the database def add_door_to_database(db_file, group_cn, Door_id): + """ + Add a door to the database. + + This function inserts a new door record into the Doors table with the specified group common name (cn) + and door ID. + + ## Parameters: + - db_file (str): The file path to the SQLite database. + - group_cn (str): The common name of the group associated with the door. + - Door_id (int): The ID of the door. + + ## Returns: + - bool: True if the door was added successfully, False otherwise. + ## Raises: + - sqlite3.Error: If there's an error executing the SQL query. + """ try: conn = sqlite3.connect(db_file) cursor = conn.cursor() @@ -285,6 +425,22 @@ def add_door_to_database(db_file, group_cn, Door_id): # Function to verify if the user is allowed to open the door def check_access(rfid_uid_str, door_id): + """ + Check if the user is allowed to open the door. + + This function verifies if the user associated with the given RFID UID is allowed to open the door + specified by the door ID. + + ## Parameters: + - rfid_uid_str (str): The RFID UID of the user. + - door_id (int): The ID of the door. + + ## Returns: + - tuple: A tuple containing a boolean value indicating access permission and the user's UPN + if access is granted, otherwise (False, None). + ## Raises: + - sqlite3.Error: If there's an error executing the SQL query. + """ try: conn = sqlite3.connect(DBFILE) # Update with your database file path cursor = conn.cursor() diff --git a/Server/Program/ldapSync.py b/Server/Program/ldapSync.py index 636607d..da18779 100644 --- a/Server/Program/ldapSync.py +++ b/Server/Program/ldapSync.py @@ -9,12 +9,14 @@ from env import DOOR_ACCESS_GROUPS_DN, LDAPPASS, LDAPUSER, LDAP_SERVER, USERS_DN # Function to initialize LDAP connection def initialize_ldap_connection(): """ - ## Settings : - None - ## Behavior - Init the connection to the LDAP server. - Return LDAPobjet instance when connected - if it fail, return None and print error code + Initialize the LDAP connection. + + This function attempts to establish a connection to the LDAP server using the provided server address, + user credentials, and settings. If the connection is successful, it returns the connection object. + In case of an error, it prints the error and returns None. + + ## Returns: + - ldap.LDAPObject or None: The LDAP connection object if successful, otherwise None. """ try: connect = ldap.initialize(LDAP_SERVER) @@ -30,12 +32,18 @@ def initialize_ldap_connection(): # Function to retrieve users from LDAP def retrieve_users_from_ldap(ldap_connection): """ - ## Settings : - - ldap_connection : LDAPobjet instance - ## Behavior - retrieve the users in the specified OU - Return result when it success - if it fail, return empty list and print error code + Retrieve users from LDAP. + + This function searches the LDAP directory for users within the specified base DN and returns the search result. + It searches for objects with the 'user' object class within the subtree of the specified base DN. + + ## Parameters: + - ldap_connection (ldap.LDAPObject): The LDAP connection object. + + ## Returns: + - list of tuple: A list of tuples containing the search result, where each tuple represents a user entry. + Each tuple consists of the DN (Distinguished Name) of the user entry and its attributes. + Returns an empty list if an error occurs during the LDAP search. """ try: result = ldap_connection.search_s( @@ -50,12 +58,18 @@ def retrieve_users_from_ldap(ldap_connection): # Function to retrieve groups from LDAP def retrieve_groups_from_ldap(ldap_connection): """ - ## Settings : - - ldap_connection : LDAPobjet instance - ## Behavior - retrieve the groups in the specified OU - Return result when it success - if it fail, return empty list and print error code + Retrieve groups from LDAP. + + This function searches the LDAP directory for groups within the specified base DN and returns the search result. + It searches for objects with the 'group' object class within the subtree of the specified base DN. + + ## Parameters: + - ldap_connection (ldap.LDAPObject): The LDAP connection object. + + ## Returns: + - list of tuple: A list of tuples containing the search result, where each tuple represents a group entry. + Each tuple consists of the DN (Distinguished Name) of the group entry and its attributes. + Returns an empty list if an error occurs during the LDAP search. """ try: result = ldap_connection.search_s( @@ -69,6 +83,26 @@ def retrieve_groups_from_ldap(ldap_connection): # Function to add user to the database or update if already exists def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): + """ + Add a user to the database or update the user's information if they already exist. + + This function checks if a user with the given UPN (User Principal Name) already exists in the database. + If the user exists and their RFID UID or group membership has changed, the function updates the user's + record. If the user does not exist, the function inserts a new record for the user. + + ## Parameters: + - conn (sqlite3.Connection): The SQLite database connection. + - cursor (sqlite3.Cursor): The cursor object for executing SQL queries. + - upn (str): The User Principal Name of the user. + - rfid_uid (str): The RFID UID associated with the user. + - member_of (str): The group membership (CN) of the user. + + ## Returns: + - None + + ## Raises: + - sqlite3.Error: If an error occurs while accessing the SQLite database. + """ try: cursor.execute("SELECT * FROM Users WHERE upn=?", (upn,)) existing_user = cursor.fetchone() @@ -99,6 +133,24 @@ def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): # Function to add group to the database or update if already exists def add_group_to_database(conn, cursor, cn): + """ + Add a group to the database if it does not already exist. + + This function checks if a group with the given CN (Common Name) already exists in the database. + If the group exists, it prints a message indicating that the group already exists. If the group + does not exist, it inserts a new record for the group. + + Parameters: + conn (sqlite3.Connection): The SQLite database connection. + cursor (sqlite3.Cursor): The cursor object for executing SQL queries. + cn (str): The Common Name of the group. + + Returns: + None + + Raises: + sqlite3.Error: If an error occurs while accessing the SQLite database. + """ try: cursor.execute("SELECT * FROM Groups WHERE cn=?", (cn,)) existing_group = cursor.fetchone() @@ -184,10 +236,35 @@ def sync_ldap_to_database(db_file): def run_sync_ldap_to_database_thread(db_file): + """ + Run the LDAP synchronization process in a separate thread. + + This function initiates the synchronization of LDAP data to the database in a background thread. + It ensures that the LDAP synchronization runs asynchronously, allowing the main program to continue + running without being blocked. + + Parameters: + db_file (str): The path to the SQLite database file. + + Returns: + None + """ print(f"[{datetime.now()}] Running LDAP sync") threading.Thread(target=sync_ldap_to_database, args=(db_file,), daemon=True).start() def schedule_sync_ldap_to_database(db_file): + """ + Schedule the LDAP synchronization process to run immediately and then every 5 minutes. + + This function runs the LDAP synchronization process in a background thread immediately upon invocation + and sets up a recurring schedule to run the synchronization every 5 minutes. + + Parameters: + db_file (str): The path to the SQLite database file. + + Returns: + None + """ run_sync_ldap_to_database_thread(db_file) # Run immediately schedule.every(5).minutes.do(run_sync_ldap_to_database_thread, db_file) diff --git a/Server/Program/server.py b/Server/Program/server.py index 0f9cfb4..6ab3537 100644 --- a/Server/Program/server.py +++ b/Server/Program/server.py @@ -1,12 +1,11 @@ from ldapSync import schedule_sync_ldap_to_database -from database import setup_database, print_database_content +from database import setup_database from Webserver import run_webServer_thread from env import DBFILE import schedule setup_database(DBFILE) -# print_database_content(DBFILE) run_webServer_thread() schedule_sync_ldap_to_database(DBFILE) From 44acb1a0be466e4ad64f68a98bbfcfaa0702340d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Gon=C3=A7alves=20Monnet?= Date: Fri, 7 Jun 2024 11:40:36 +0200 Subject: [PATCH 3/5] Update code formating --- Server/Program/Webserver.py | 39 ++++++++---------- Server/Program/database.py | 80 ++++++++++++++----------------------- Server/Program/ldapSync.py | 73 +++++++++++++++++++-------------- Server/Program/server.py | 9 ++--- 4 files changed, 94 insertions(+), 107 deletions(-) diff --git a/Server/Program/Webserver.py b/Server/Program/Webserver.py index 2bfa601..3e0efee 100644 --- a/Server/Program/Webserver.py +++ b/Server/Program/Webserver.py @@ -1,15 +1,6 @@ -from threading import Thread -from flask import ( - Flask, - render_template, - send_file, - Response, - request, - redirect, - jsonify, -) import io -from ldapSync import sync_ldap_to_database +from threading import Thread + from database import ( add_door_to_database, check_access, @@ -22,6 +13,15 @@ from database import ( log_access_attempt, ) from env import DBFILE, WebServerPORT +from flask import ( + Flask, + Response, + jsonify, + redirect, + render_template, + request, +) +from ldapSync import sync_ldap_to_database app = Flask(__name__) @@ -92,11 +92,9 @@ def add_door(): group_cn = request.form["group_cn"] # Update with your database file path - exec = add_door_to_database(DBFILE, group_cn, Door_id) if add_door_to_database(DBFILE, group_cn, Door_id): return redirect("/") - else: - return f"Failed to add door to the database." + return "Failed to add door to the database." # Route to handle sync button click @@ -121,14 +119,12 @@ def door_access(): log_access_attempt(DBFILE, upn, rfid_uid, True, door_id) return jsonify({"access_granted": True, "upn": upn}), 200 - else: - log_access_attempt(DBFILE, upn, rfid_uid, False, door_id) - return jsonify({"access_granted": False}), 403 + log_access_attempt(DBFILE, upn, rfid_uid, False, door_id) + return jsonify({"access_granted": False}), 403 def run_flask_app(): - """ - Run the Flask web application. + """Run the Flask web application. This function starts the Flask web application with debugging enabled, no reloader, on the specified port and host. It serves as the main entry @@ -138,13 +134,12 @@ def run_flask_app(): def run_webServer_thread(): - """ - Start the Flask web server in a separate thread. + """Start the Flask web server in a separate thread. This function initializes and starts a new thread to run the Flask web application. It allows the web server to run concurrently with other tasks in the main program, ensuring the web interface remains responsive. - """ + """ print(f"STARTING WEB SERVER ON PORT {WebServerPORT}") flask_thread = Thread(target=run_flask_app, daemon=True) flask_thread.start() diff --git a/Server/Program/database.py b/Server/Program/database.py index 96c72e2..ade777e 100644 --- a/Server/Program/database.py +++ b/Server/Program/database.py @@ -1,12 +1,12 @@ -from datetime import datetime import sqlite3 +from datetime import datetime + from env import DBFILE # Function to check if a table exists in the database def table_exists(cursor, table_name): - """ - Check if a table exists in the database. + """Check if a table exists in the database. This function checks whether a table with the specified name exists in the database. @@ -18,15 +18,15 @@ def table_exists(cursor, table_name): - bool: True if the table exists, False otherwise. """ cursor.execute( - "SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,) + "SELECT name FROM sqlite_master WHERE type='table' AND name=?", + (table_name,), ) return cursor.fetchone() is not None # Function to create the Users table def create_users_table(cursor): - """ - Create the Users table in the database. + """Create the Users table in the database. This function creates the Users table with columns for user principal name (upn), RFID UID, and member of groups. @@ -43,8 +43,7 @@ def create_users_table(cursor): # Function to create the Groups table def create_groups_table(cursor): - """ - Create the Groups table in the database. + """Create the Groups table in the database. This function creates the Groups table with a single column for common name (cn) of the group. @@ -58,8 +57,7 @@ def create_groups_table(cursor): # Function to create the Doors table def create_doors_table(cursor): - """ - Create the Doors table in the database. + """Create the Doors table in the database. This function creates the Doors table with columns for door ID and associated group common name. @@ -75,8 +73,7 @@ def create_doors_table(cursor): # Function to create the logs table def create_logs_table(cursor): - """ - Create the logs table in the database. + """Create the logs table in the database. This function creates the logs table with columns for ID (auto-incremented), timestamp, user, RFID UID, door ID, and access granted status. Foreign key constraints are set on the door ID, user, and RFID UID columns. @@ -101,8 +98,7 @@ def create_logs_table(cursor): # Function to setup the database def setup_database(db_file): - """ - Set up the SQLite database by creating necessary tables if they don't already exist. + """Set up the SQLite database by creating necessary tables if they don't already exist. This function checks if the Users, Groups, Doors, and Log tables exist in the database. If any of them don't exist, it creates them using their respective creation functions. After creating or verifying the tables, it commits @@ -150,8 +146,7 @@ def setup_database(db_file): def log_access_attempt(db_file, user, rFIDUID, granted, doorID): - """ - Log an access attempt to the database. + """Log an access attempt to the database. This function inserts a new entry into the log table of the SQLite database, recording details about the access attempt, such as the timestamp, user, RFID UID, whether access was granted, and the door ID. @@ -182,8 +177,7 @@ def log_access_attempt(db_file, user, rFIDUID, granted, doorID): def print_users_table(cursor): - """ - Print the content of the Users table. + """Print the content of the Users table. ## Parameters: - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. @@ -197,12 +191,11 @@ def print_users_table(cursor): # Function to print the content of the Groups table def print_groups_table(cursor): - """ - Print the content of the Groups table. + """Print the content of the Groups table. ## Parameters: - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. - """ + """ cursor.execute("SELECT * FROM Groups") rows = cursor.fetchall() print("Groups:") @@ -212,8 +205,7 @@ def print_groups_table(cursor): # Function to print the content of the Doors table def print_doors_table(cursor): - """ - Print the content of the Doors table. + """Print the content of the Doors table. ## Parameters: - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. @@ -227,8 +219,7 @@ def print_doors_table(cursor): # Function to print the content of the Log table def print_log_table(cursor): - """ - Print the content of the Log table. + """Print the content of the Log table. ## Parameters: - cursor (sqlite3.Cursor): Cursor object for executing SQLite queries. @@ -242,8 +233,7 @@ def print_log_table(cursor): # Function to print the content of the entire database def print_database_content(db_file): - """ - Print the content of the entire database. + """Print the content of the entire database. ## Parameters: - db_file (str): The file path to the SQLite database. @@ -260,9 +250,8 @@ def print_database_content(db_file): def get_logs(): - """ - Fetch all logs from the log table in the database. - + """Fetch all logs from the log table in the database. + ## Returns: - list: List of log records. """ @@ -282,8 +271,7 @@ def get_logs(): def get_latest_logs(db_file, limit=10): - """ - Fetch the latest logs from the database. + """Fetch the latest logs from the database. ## Parameters: - db_file (str): The file path to the SQLite database. @@ -312,10 +300,9 @@ def get_latest_logs(db_file, limit=10): # Function to fetch list of existing groups from the database def get_existing_groups(db_file): - """ - Fetches a list of existing groups from the database. + """Fetches a list of existing groups from the database. - ## Parameters: + ## Parameters: - db_file (str): The file path to the SQLite database. ## Returns: @@ -334,8 +321,7 @@ def get_existing_groups(db_file): def delete_group_from_database(group_cn): - """ - Delete a group from the database. + """Delete a group from the database. This function deletes a group with the specified common name (cn) from both the Groups and Doors tables in the database. @@ -352,8 +338,7 @@ def delete_group_from_database(group_cn): def get_doors(): - """ - Retrieve all doors from the database. + """Retrieve all doors from the database. This function fetches all rows from the Doors table in the database and returns them as a list of tuples. @@ -369,9 +354,8 @@ def get_doors(): def get_users(): - """ - Fetch all users from the Users table in the database. - + """Fetch all users from the Users table in the database. + ## Returns: - list: List of user records. """ @@ -387,8 +371,7 @@ def get_users(): # Function to add a door to the database def add_door_to_database(db_file, group_cn, Door_id): - """ - Add a door to the database. + """Add a door to the database. This function inserts a new door record into the Doors table with the specified group common name (cn) and door ID. @@ -425,8 +408,7 @@ def add_door_to_database(db_file, group_cn, Door_id): # Function to verify if the user is allowed to open the door def check_access(rfid_uid_str, door_id): - """ - Check if the user is allowed to open the door. + """Check if the user is allowed to open the door. This function verifies if the user associated with the given RFID UID is allowed to open the door specified by the door ID. @@ -450,7 +432,8 @@ def check_access(rfid_uid_str, door_id): # Get the user's UPN and group memberships based on the RFID UID cursor.execute( - "SELECT upn, MemberOf FROM Users WHERE rFIDUID = ?", (rfid_uid_bytes,) + "SELECT upn, MemberOf FROM Users WHERE rFIDUID = ?", + (rfid_uid_bytes,), ) user_data = cursor.fetchone() if user_data is None: @@ -472,8 +455,7 @@ def check_access(rfid_uid_str, door_id): # Check if the user's group is allowed to open the door if door_group in user_groups.split(","): return True, upn # Access granted - else: - return False, None # Access denied + return False, None # Access denied except sqlite3.Error as e: print(f"SQLite Error: {e}") diff --git a/Server/Program/ldapSync.py b/Server/Program/ldapSync.py index da18779..eb0576e 100644 --- a/Server/Program/ldapSync.py +++ b/Server/Program/ldapSync.py @@ -1,15 +1,15 @@ -from datetime import datetime -import ldap import sqlite3 import threading +from datetime import datetime + +import ldap import schedule -from env import DOOR_ACCESS_GROUPS_DN, LDAPPASS, LDAPUSER, LDAP_SERVER, USERS_DN +from env import DOOR_ACCESS_GROUPS_DN, LDAP_SERVER, LDAPPASS, LDAPUSER, USERS_DN # Function to initialize LDAP connection def initialize_ldap_connection(): - """ - Initialize the LDAP connection. + """Initialize the LDAP connection. This function attempts to establish a connection to the LDAP server using the provided server address, user credentials, and settings. If the connection is successful, it returns the connection object. @@ -31,8 +31,7 @@ def initialize_ldap_connection(): # Function to retrieve users from LDAP def retrieve_users_from_ldap(ldap_connection): - """ - Retrieve users from LDAP. + """Retrieve users from LDAP. This function searches the LDAP directory for users within the specified base DN and returns the search result. It searches for objects with the 'user' object class within the subtree of the specified base DN. @@ -47,7 +46,9 @@ def retrieve_users_from_ldap(ldap_connection): """ try: result = ldap_connection.search_s( - USERS_DN, ldap.SCOPE_SUBTREE, "(objectClass=user)" + USERS_DN, + ldap.SCOPE_SUBTREE, + "(objectClass=user)", ) return result except ldap.LDAPError as e: @@ -57,8 +58,7 @@ def retrieve_users_from_ldap(ldap_connection): # Function to retrieve groups from LDAP def retrieve_groups_from_ldap(ldap_connection): - """ - Retrieve groups from LDAP. + """Retrieve groups from LDAP. This function searches the LDAP directory for groups within the specified base DN and returns the search result. It searches for objects with the 'group' object class within the subtree of the specified base DN. @@ -73,7 +73,9 @@ def retrieve_groups_from_ldap(ldap_connection): """ try: result = ldap_connection.search_s( - DOOR_ACCESS_GROUPS_DN, ldap.SCOPE_SUBTREE, "(objectClass=group)" + DOOR_ACCESS_GROUPS_DN, + ldap.SCOPE_SUBTREE, + "(objectClass=group)", ) return result except ldap.LDAPError as e: @@ -83,8 +85,7 @@ def retrieve_groups_from_ldap(ldap_connection): # Function to add user to the database or update if already exists def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): - """ - Add a user to the database or update the user's information if they already exist. + """Add a user to the database or update the user's information if they already exist. This function checks if a user with the given UPN (User Principal Name) already exists in the database. If the user exists and their RFID UID or group membership has changed, the function updates the user's @@ -117,7 +118,7 @@ def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): print(f"[{datetime.now()}] User '{upn}' updated in the database.") else: print( - f"[{datetime.now()}] User '{upn}' already exists in the database with the same data." + f"[{datetime.now()}] User '{upn}' already exists in the database with the same data.", ) else: # User doesn't exist, insert new user @@ -133,23 +134,26 @@ def add_user_to_database(conn, cursor, upn, rfid_uid, member_of): # Function to add group to the database or update if already exists def add_group_to_database(conn, cursor, cn): - """ - Add a group to the database if it does not already exist. + """Add a group to the database if it does not already exist. This function checks if a group with the given CN (Common Name) already exists in the database. If the group exists, it prints a message indicating that the group already exists. If the group does not exist, it inserts a new record for the group. - Parameters: + Parameters + ---------- conn (sqlite3.Connection): The SQLite database connection. cursor (sqlite3.Cursor): The cursor object for executing SQL queries. cn (str): The Common Name of the group. - Returns: + Returns + ------- None - Raises: + Raises + ------ sqlite3.Error: If an error occurs while accessing the SQLite database. + """ try: cursor.execute("SELECT * FROM Groups WHERE cn=?", (cn,)) @@ -168,13 +172,14 @@ def add_group_to_database(conn, cursor, cn): # Function to sync LDAP users and groups to the database def sync_ldap_to_database(db_file): - """ - Syncs LDAP users and groups to the SQLite database. + """Syncs LDAP users and groups to the SQLite database. Args: + ---- db_file (str): The path to the SQLite database file. Returns: + ------- None This function connects to the LDAP server, retrieves user and group information, @@ -183,8 +188,10 @@ def sync_ldap_to_database(db_file): and groups are added or updated in the database according to the LDAP information. Note: + ---- The LDAP connection must be properly configured and the LDAP server accessible from the machine running this script. + """ ldap_conn = initialize_ldap_connection() if ldap_conn: @@ -213,11 +220,11 @@ def sync_ldap_to_database(db_file): cursor.execute("DELETE FROM Users WHERE upn=?", (upn,)) conn.commit() print( - f"[{datetime.now()}] User '{upn}' disabled in LDAP and removed from the database." + f"[{datetime.now()}] User '{upn}' disabled in LDAP and removed from the database.", ) else: print( - f"[{datetime.now()}] User '{upn}' disabled in LDAP but not present in the database." + f"[{datetime.now()}] User '{upn}' disabled in LDAP but not present in the database.", ) continue # Skip adding the disabled user to the database @@ -236,35 +243,39 @@ def sync_ldap_to_database(db_file): def run_sync_ldap_to_database_thread(db_file): - """ - Run the LDAP synchronization process in a separate thread. + """Run the LDAP synchronization process in a separate thread. This function initiates the synchronization of LDAP data to the database in a background thread. It ensures that the LDAP synchronization runs asynchronously, allowing the main program to continue running without being blocked. - Parameters: + Parameters + ---------- db_file (str): The path to the SQLite database file. - Returns: + Returns + ------- None + """ print(f"[{datetime.now()}] Running LDAP sync") threading.Thread(target=sync_ldap_to_database, args=(db_file,), daemon=True).start() def schedule_sync_ldap_to_database(db_file): - """ - Schedule the LDAP synchronization process to run immediately and then every 5 minutes. + """Schedule the LDAP synchronization process to run immediately and then every 5 minutes. This function runs the LDAP synchronization process in a background thread immediately upon invocation and sets up a recurring schedule to run the synchronization every 5 minutes. - Parameters: + Parameters + ---------- db_file (str): The path to the SQLite database file. - Returns: + Returns + ------- None + """ run_sync_ldap_to_database_thread(db_file) # Run immediately schedule.every(5).minutes.do(run_sync_ldap_to_database_thread, db_file) diff --git a/Server/Program/server.py b/Server/Program/server.py index 6ab3537..0798456 100644 --- a/Server/Program/server.py +++ b/Server/Program/server.py @@ -1,9 +1,8 @@ -from ldapSync import schedule_sync_ldap_to_database -from database import setup_database -from Webserver import run_webServer_thread -from env import DBFILE import schedule - +from database import setup_database +from env import DBFILE +from ldapSync import schedule_sync_ldap_to_database +from Webserver import run_webServer_thread setup_database(DBFILE) run_webServer_thread() From 06dcd473f2ae9b35209bce64abf68f4ab08262d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Gon=C3=A7alves=20Monnet?= Date: Fri, 7 Jun 2024 14:18:26 +0200 Subject: [PATCH 4/5] Add docstrings --- Client/main.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/Client/main.py b/Client/main.py index b072d6e..5c073ea 100644 --- a/Client/main.py +++ b/Client/main.py @@ -35,6 +35,15 @@ inactivity_timer = Timer(-1) def init_oled(): + """ + Initialize the OLED display. + + This function initializes the OLED display with a width of 128 pixels, height of 64 pixels, + and communicates over the specified I2C interface. + + ## Raises: + - Exception: If there's an error initializing the OLED display. + """ global oled try: oled = SSD1306_I2C(128, 64, i2c) @@ -47,6 +56,18 @@ def init_oled(): def display_message(message, ip_address): + """ + Display a message on the OLED screen. + + This function displays a message on the OLED screen along with the IP address. + + ## Parameters: + - message (str): The message to be displayed. + - ip_address (str): The IP address to be displayed. + + ## Raises: + - Exception: If there's an error displaying the message on the OLED screen. + """ global last_activity_time, screensaver_active, screensaver_thread_running last_activity_time = time.time() screensaver_active = False @@ -70,6 +91,17 @@ def display_message(message, ip_address): def screensaver(): + """ + Activate the screensaver with RF-AD animation. + + This function activates the screensaver by displaying an RF-AD animation moving across the screen. + + ## Global Variables: + - screensaver_active (bool): Flag indicating if the screensaver is active. + - screensaver_thread_running (bool): Flag indicating if the screensaver thread is running. + - last_activity_time (float): Timestamp of the last activity. + + """ global screensaver_active, screensaver_thread_running x, y = 0, 0 direction_x, direction_y = 1, 1 @@ -95,6 +127,16 @@ def screensaver(): def start_screensaver_thread(): + """ + Start the screensaver thread if it's not already running. + + This function starts the screensaver thread if it's not already running. It sets flags to indicate + the screensaver is active and the thread is running. + + ## Global Variables: + - screensaver_active (bool): Flag indicating if the screensaver is active. + - screensaver_thread_running (bool): Flag indicating if the screensaver thread is running. + """ global screensaver_active, screensaver_thread_running if not screensaver_thread_running: screensaver_active = True @@ -102,17 +144,43 @@ def start_screensaver_thread(): _thread.start_new_thread(screensaver, ()) -def handle_inactivity(timer): +def handle_inactivity(): + """ + Handle user inactivity by starting the screensaver if necessary. + + This function is called by a timer to check for user inactivity. If the specified time period + has passed since the last activity, it starts the screensaver thread. + + """ if time.time() - last_activity_time > 60: start_screensaver_thread() def reset_inactivity_timer(): + """ + Reset the inactivity timer. + + This function resets the last activity time to the current time, effectively restarting the + inactivity timer. + """ global last_activity_time last_activity_time = time.time() def test_server_connection(ip_address): + """ + Test the connection to the server and handle connection errors. + + This function tests the connection to the server by sending an HTTP GET request to the server + endpoint. It handles connection errors and displays appropriate messages on the OLED screen. + + ## Parameters: + - ip_address (str): The IP address of the server. + + ## Global Variables: + - SERVER_IP (str): The IP address of the server. + - SERVER_PORT (int): The port number of the server. + """ while True: try: response = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/") @@ -145,6 +213,21 @@ def test_server_connection(ip_address): # Connect to WiFi def connect_wifi(ssid, password): + """ + Connect to a WiFi network. + + This function connects the device to the specified WiFi network using the provided SSID and password. + It waits until the connection is established and then displays a message on the OLED screen indicating + successful connection. + + ## Parameters: + - ssid (str): The SSID of the WiFi network. + - password (str): The password of the WiFi network. + + ## Global Variables: + - SERVER_IP (str): The IP address of the server. + - SERVER_PORT (int): The port number of the server. + """ wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) @@ -161,6 +244,18 @@ def connect_wifi(ssid, password): # Function to send RFID UID to the server def send_rfid_to_server(rfid_uid): + """ + Send RFID UID to the server for access verification. + + This function constructs a JSON payload containing the RFID UID and the door ID, and sends it to the server + for access verification. It expects a JSON response from the server indicating whether access is granted. + + ## Parameters: + - rfid_uid (str): The RFID UID to be sent to the server. + + ## Returns: + - dict: A dictionary containing the response from the server, indicating whether access is granted. + """ try: url = f"http://{SERVER_IP}:{SERVER_PORT}/access" headers = {"Content-Type": "application/json"} @@ -175,6 +270,15 @@ def send_rfid_to_server(rfid_uid): # Main loop to scan RFID tags def main(): + """ + Main loop to scan RFID tags and handle access control. + + This function initializes the OLED display, connects to WiFi, and starts a loop to scan RFID tags. + It handles user authentication by sending the RFID UID to the server and displaying access status on the OLED. + + The function also sets up an inactivity timer to activate a screensaver after 1 minute of inactivity. + + """ # Retry mechanism for OLED initialization for _ in range(3): try: From a24d302400f169356b396e20fca9891c4e46e93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Gon=C3=A7alves=20Monnet?= Date: Fri, 7 Jun 2024 16:34:07 +0200 Subject: [PATCH 5/5] Remove test echo --- Server/Program/entrypoint.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/Server/Program/entrypoint.sh b/Server/Program/entrypoint.sh index 2f09463..4c585e9 100644 --- a/Server/Program/entrypoint.sh +++ b/Server/Program/entrypoint.sh @@ -1,6 +1,5 @@ #!/bin/sh ls /app/Program -echo patate # Create env.py with environment variables cat < /app/env.py LDAPUSER = "${LDAPUSER}"