From 7e18f84c425fcdb9552279c2bc747a1039e56569 Mon Sep 17 00:00:00 2001 From: Atakan Kayman Date: Sun, 31 May 2026 15:54:46 +0300 Subject: [PATCH] added postgresql integration for user permissions --- .../IUserRepository.cs | 4 +- .../JsonUserRepository.cs | 24 +- UserPermissionTest_CS_WinForms/MainForm.cs | 22 +- UserPermissionTest_CS_WinForms/Permission.cs | 24 + .../PostgreSqlUserRepository.cs | 444 ++++++++++++++++++ .../SessionManager.cs | 12 +- UserPermissionTest_CS_WinForms/User.cs | 4 +- .../UserPermissionTest_CS_WinForms.csproj | 1 + .../UserSettings.cs | 34 +- winforms_security_sandbox.md | 92 ++-- 10 files changed, 558 insertions(+), 103 deletions(-) create mode 100644 UserPermissionTest_CS_WinForms/Permission.cs create mode 100644 UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs diff --git a/UserPermissionTest_CS_WinForms/IUserRepository.cs b/UserPermissionTest_CS_WinForms/IUserRepository.cs index ab3f873..a12eda7 100644 --- a/UserPermissionTest_CS_WinForms/IUserRepository.cs +++ b/UserPermissionTest_CS_WinForms/IUserRepository.cs @@ -6,7 +6,7 @@ namespace UserPermissionTest_CS_WinForms { List LoadUsers(); void SaveUsers(List users); - List LoadPermissions(); - void SavePermissions(List permissions); + List LoadPermissions(); + void SavePermissions(List permissions); } } diff --git a/UserPermissionTest_CS_WinForms/JsonUserRepository.cs b/UserPermissionTest_CS_WinForms/JsonUserRepository.cs index 7541664..8b4b1e7 100644 --- a/UserPermissionTest_CS_WinForms/JsonUserRepository.cs +++ b/UserPermissionTest_CS_WinForms/JsonUserRepository.cs @@ -74,7 +74,7 @@ namespace UserPermissionTest_CS_WinForms } } - public List LoadPermissions() + public List LoadPermissions() { if (!File.Exists(PermissionsFile)) { @@ -86,7 +86,7 @@ namespace UserPermissionTest_CS_WinForms try { string json = File.ReadAllText(PermissionsFile); - var permissions = JsonConvert.DeserializeObject>(json); + var permissions = JsonConvert.DeserializeObject>(json); return permissions ?? GetDefaultPermissions(); } catch (Exception ex) @@ -96,7 +96,7 @@ namespace UserPermissionTest_CS_WinForms } } - public void SavePermissions(List permissions) + public void SavePermissions(List permissions) { try { @@ -118,27 +118,27 @@ namespace UserPermissionTest_CS_WinForms Username = "admin", FullName = "System Administrator", Password = PasswordHasher.HashPassword("admin"), - Permissions = GetDefaultPermissions() + Permissions = new List { 1, 2, 3, 4, 5 } }, new User { Username = "user", FullName = "Standard User", Password = PasswordHasher.HashPassword("user"), - Permissions = new List { "View Dashboard" } + Permissions = new List { 1 } } }; } - private List GetDefaultPermissions() + private List GetDefaultPermissions() { - return new List + return new List { - "View Dashboard", - "Edit Settings", - "Manage Users", - "Full Control", - "Delete Transactions" + new Permission { Id = 1, Name = "View Dashboard" }, + new Permission { Id = 2, Name = "Edit Settings" }, + new Permission { Id = 3, Name = "Manage Users" }, + new Permission { Id = 4, Name = "Full Control" }, + new Permission { Id = 5, Name = "Delete Transactions" } }; } } diff --git a/UserPermissionTest_CS_WinForms/MainForm.cs b/UserPermissionTest_CS_WinForms/MainForm.cs index 71d95eb..a38d77a 100644 --- a/UserPermissionTest_CS_WinForms/MainForm.cs +++ b/UserPermissionTest_CS_WinForms/MainForm.cs @@ -66,9 +66,11 @@ namespace UserPermissionTest_CS_WinForms } else { - foreach (var permission in user.Permissions) + foreach (var permissionId in user.Permissions) { - lstUserPermissions.Items.Add("✓ " + permission); + var permObj = SessionManager.AvailablePermissions.FirstOrDefault(p => p.Id == permissionId); + string permName = permObj != null ? permObj.Name : $"Unknown (ID: {permissionId})"; + lstUserPermissions.Items.Add("✓ " + permName); } } @@ -76,7 +78,11 @@ namespace UserPermissionTest_CS_WinForms btnLogout.Enabled = true; // Authorization: Check if user has 'Manage Users' or 'Full Control' - bool hasAccess = user.Permissions.Contains("Manage Users") || user.Permissions.Contains("Full Control"); + var manageUsersPerm = SessionManager.AvailablePermissions.FirstOrDefault(p => p.Name.Equals("Manage Users", StringComparison.OrdinalIgnoreCase)); + var fullControlPerm = SessionManager.AvailablePermissions.FirstOrDefault(p => p.Name.Equals("Full Control", StringComparison.OrdinalIgnoreCase)); + + bool hasAccess = (manageUsersPerm != null && user.Permissions.Contains(manageUsersPerm.Id)) || + (fullControlPerm != null && user.Permissions.Contains(fullControlPerm.Id)); if (hasAccess) { btnUsers.Enabled = true; @@ -124,7 +130,15 @@ namespace UserPermissionTest_CS_WinForms private void btnUsers_Click(object sender, EventArgs e) { var user = SessionManager.CurrentUser; - if (user == null || (!user.Permissions.Contains("Manage Users") && !user.Permissions.Contains("Full Control"))) + var manageUsersPerm = SessionManager.AvailablePermissions.FirstOrDefault(p => p.Name.Equals("Manage Users", StringComparison.OrdinalIgnoreCase)); + var fullControlPerm = SessionManager.AvailablePermissions.FirstOrDefault(p => p.Name.Equals("Full Control", StringComparison.OrdinalIgnoreCase)); + + bool hasAccess = user != null && ( + (manageUsersPerm != null && user.Permissions.Contains(manageUsersPerm.Id)) || + (fullControlPerm != null && user.Permissions.Contains(fullControlPerm.Id)) + ); + + if (!hasAccess) { MessageBox.Show( "Security Exception: You do not possess the required credentials ('Manage Users' or 'Full Control') to access directory configuration.", diff --git a/UserPermissionTest_CS_WinForms/Permission.cs b/UserPermissionTest_CS_WinForms/Permission.cs new file mode 100644 index 0000000..4e4a8b7 --- /dev/null +++ b/UserPermissionTest_CS_WinForms/Permission.cs @@ -0,0 +1,24 @@ +using System; + +namespace UserPermissionTest_CS_WinForms +{ + public class Permission + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + + public override string ToString() + { + return Name; + } + + public Permission Clone() + { + return new Permission + { + Id = this.Id, + Name = this.Name + }; + } + } +} diff --git a/UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs b/UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs new file mode 100644 index 0000000..ed1cd3b --- /dev/null +++ b/UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs @@ -0,0 +1,444 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Npgsql; + +namespace UserPermissionTest_CS_WinForms +{ + public class PostgreSqlUserRepository : IUserRepository + { + private readonly string _connectionString; + + public PostgreSqlUserRepository(string connectionString) + { + _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + InitializeDatabaseSchema(); + } + + private void InitializeDatabaseSchema() + { + using (var conn = new NpgsqlConnection(_connectionString)) + { + conn.Open(); + + // Dynamic Migration Drop: If available_permissions exists with old text primary key structure + bool needsRebuild = false; + try + { + // Check if the table has the 'id' column. If not, this throws an exception, triggering the rebuild! + using (var cmd = new NpgsqlCommand("SELECT id FROM available_permissions LIMIT 1;", conn)) + { + cmd.ExecuteNonQuery(); + } + } + catch + { + needsRebuild = true; + } + + if (needsRebuild) + { + using (var cmd = new NpgsqlCommand(@" + DROP TABLE IF EXISTS user_permissions; + DROP TABLE IF EXISTS available_permissions;", conn)) + { + cmd.ExecuteNonQuery(); + } + } + + // Create users table + using (var cmd = new NpgsqlCommand(@" + CREATE TABLE IF NOT EXISTS users ( + username VARCHAR(50) PRIMARY KEY, + fullname VARCHAR(100) NOT NULL, + password VARCHAR(255) NOT NULL + );", conn)) + { + cmd.ExecuteNonQuery(); + } + + // Create available_permissions table (ID-based) + using (var cmd = new NpgsqlCommand(@" + CREATE TABLE IF NOT EXISTS available_permissions ( + id INT PRIMARY KEY, + name VARCHAR(100) UNIQUE NOT NULL + );", conn)) + { + cmd.ExecuteNonQuery(); + } + + // Create user_permissions table (mapping username to permission_id) + using (var cmd = new NpgsqlCommand(@" + CREATE TABLE IF NOT EXISTS user_permissions ( + username VARCHAR(50) REFERENCES users(username) ON DELETE CASCADE, + permission_id INT REFERENCES available_permissions(id) ON DELETE CASCADE, + PRIMARY KEY (username, permission_id) + );", conn)) + { + cmd.ExecuteNonQuery(); + } + + // 1. Auto-Seed default available permissions if empty + using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM available_permissions;", conn)) + { + long count = (long)cmd.ExecuteScalar(); + if (count == 0) + { + foreach (var perm in GetDefaultPermissions()) + { + using (var insertCmd = new NpgsqlCommand(@" + INSERT INTO available_permissions (id, name) + VALUES (@id, @name) + ON CONFLICT (id) DO NOTHING;", conn)) + { + insertCmd.Parameters.AddWithValue("@id", perm.Id); + insertCmd.Parameters.AddWithValue("@name", perm.Name); + insertCmd.ExecuteNonQuery(); + } + } + } + } + + // 2. Auto-Seed default users if empty + using (var cmd = new NpgsqlCommand("SELECT COUNT(*) FROM users;", conn)) + { + long count = (long)cmd.ExecuteScalar(); + if (count == 0) + { + foreach (var user in GetDefaultUsers()) + { + using (var insertCmd = new NpgsqlCommand(@" + INSERT INTO users (username, fullname, password) + VALUES (@username, @fullname, @password) + ON CONFLICT (username) DO NOTHING;", conn)) + { + insertCmd.Parameters.AddWithValue("@username", user.Username); + insertCmd.Parameters.AddWithValue("@fullname", user.FullName); + insertCmd.Parameters.AddWithValue("@password", user.Password); + insertCmd.ExecuteNonQuery(); + } + } + } + } + + // 3. Self-healing permissions: check if 'admin' exists but has 0 permissions, and restore + using (var checkCmd = new NpgsqlCommand("SELECT COUNT(*) FROM users WHERE username = 'admin';", conn)) + { + long adminExists = (long)checkCmd.ExecuteScalar(); + if (adminExists > 0) + { + using (var permCountCmd = new NpgsqlCommand("SELECT COUNT(*) FROM user_permissions WHERE username = 'admin';", conn)) + { + long adminPermsCount = (long)permCountCmd.ExecuteScalar(); + if (adminPermsCount == 0) + { + foreach (int permId in new int[] { 1, 2, 3, 4, 5 }) + { + using (var insertCmd = new NpgsqlCommand(@" + INSERT INTO user_permissions (username, permission_id) + VALUES ('admin', @permId) + ON CONFLICT DO NOTHING;", conn)) + { + insertCmd.Parameters.AddWithValue("@permId", permId); + insertCmd.ExecuteNonQuery(); + } + } + } + } + } + } + + // 4. Self-healing permissions: check if 'user' exists but has 0 permissions, and restore + using (var checkCmd = new NpgsqlCommand("SELECT COUNT(*) FROM users WHERE username = 'user';", conn)) + { + long userExists = (long)checkCmd.ExecuteScalar(); + if (userExists > 0) + { + using (var permCountCmd = new NpgsqlCommand("SELECT COUNT(*) FROM user_permissions WHERE username = 'user';", conn)) + { + long userPermsCount = (long)permCountCmd.ExecuteScalar(); + if (userPermsCount == 0) + { + using (var insertCmd = new NpgsqlCommand(@" + INSERT INTO user_permissions (username, permission_id) + VALUES ('user', 1) + ON CONFLICT DO NOTHING;", conn)) + { + insertCmd.ExecuteNonQuery(); + } + } + } + } + } + } + } + + public List LoadUsers() + { + var users = new List(); + + try + { + using (var conn = new NpgsqlConnection(_connectionString)) + { + conn.Open(); + + // Read users + using (var cmd = new NpgsqlCommand("SELECT username, fullname, password FROM users;", conn)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + users.Add(new User + { + Username = reader.GetString(0), + FullName = reader.GetString(1), + Password = reader.GetString(2), + Permissions = new List() + }); + } + } + + // Read permission IDs for each user + foreach (var user in users) + { + using (var cmd = new NpgsqlCommand("SELECT permission_id FROM user_permissions WHERE username = @username;", conn)) + { + cmd.Parameters.AddWithValue("@username", user.Username); + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + user.Permissions.Add(reader.GetInt32(0)); + } + } + } + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error loading users from PostgreSQL: " + ex.Message); + } + + // Fallback if no users exist in database + if (users.Count == 0) + { + var defaults = GetDefaultUsers(); + SaveUsers(defaults); + return defaults; + } + + return users; + } + + public void SaveUsers(List users) + { + try + { + using (var conn = new NpgsqlConnection(_connectionString)) + { + conn.Open(); + + using (var trans = conn.BeginTransaction()) + { + // 1. Delete users from database that are no longer in the input list + var currentInDb = new List(); + using (var cmd = new NpgsqlCommand("SELECT username FROM users;", conn)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + currentInDb.Add(reader.GetString(0)); + } + } + + var usernamesToKeep = new HashSet(users.Select(u => u.Username), StringComparer.OrdinalIgnoreCase); + foreach (var dbUser in currentInDb) + { + if (!usernamesToKeep.Contains(dbUser)) + { + using (var cmd = new NpgsqlCommand("DELETE FROM users WHERE username = @username;", conn)) + { + cmd.Parameters.AddWithValue("@username", dbUser); + cmd.ExecuteNonQuery(); + } + } + } + + // 2. Insert or Update users and sync permissions + foreach (var user in users) + { + using (var cmd = new NpgsqlCommand(@" + INSERT INTO users (username, fullname, password) + VALUES (@username, @fullname, @password) + ON CONFLICT (username) + DO UPDATE SET fullname = EXCLUDED.fullname, password = EXCLUDED.password;", conn)) + { + cmd.Parameters.AddWithValue("@username", user.Username); + cmd.Parameters.AddWithValue("@fullname", user.FullName); + cmd.Parameters.AddWithValue("@password", user.Password); + cmd.ExecuteNonQuery(); + } + + // Re-sync permissions: clear old permissions and write current ones + using (var cmd = new NpgsqlCommand("DELETE FROM user_permissions WHERE username = @username;", conn)) + { + cmd.Parameters.AddWithValue("@username", user.Username); + cmd.ExecuteNonQuery(); + } + + foreach (var permissionId in user.Permissions) + { + using (var cmd = new NpgsqlCommand(@" + INSERT INTO user_permissions (username, permission_id) + VALUES (@username, @permissionId) + ON CONFLICT DO NOTHING;", conn)) + { + cmd.Parameters.AddWithValue("@username", user.Username); + cmd.Parameters.AddWithValue("@permissionId", permissionId); + cmd.ExecuteNonQuery(); + } + } + } + + trans.Commit(); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error saving users to PostgreSQL: " + ex.Message); + } + } + + public List LoadPermissions() + { + var permissions = new List(); + + try + { + using (var conn = new NpgsqlConnection(_connectionString)) + { + conn.Open(); + + using (var cmd = new NpgsqlCommand("SELECT id, name FROM available_permissions ORDER BY id;", conn)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + permissions.Add(new Permission + { + Id = reader.GetInt32(0), + Name = reader.GetString(1) + }); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error loading permissions from PostgreSQL: " + ex.Message); + } + + if (permissions.Count == 0) + { + var defaults = GetDefaultPermissions(); + SavePermissions(defaults); + return defaults; + } + + return permissions; + } + + public void SavePermissions(List permissions) + { + try + { + using (var conn = new NpgsqlConnection(_connectionString)) + { + conn.Open(); + + using (var trans = conn.BeginTransaction()) + { + // Sync system permissions + var currentInDb = new List(); + using (var cmd = new NpgsqlCommand("SELECT id FROM available_permissions;", conn)) + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + currentInDb.Add(reader.GetInt32(0)); + } + } + + var permissionsToKeep = new HashSet(permissions.Select(p => p.Id)); + foreach (var dbPermId in currentInDb) + { + if (!permissionsToKeep.Contains(dbPermId)) + { + using (var cmd = new NpgsqlCommand("DELETE FROM available_permissions WHERE id = @id;", conn)) + { + cmd.Parameters.AddWithValue("@id", dbPermId); + cmd.ExecuteNonQuery(); + } + } + } + + foreach (var perm in permissions) + { + using (var cmd = new NpgsqlCommand(@" + INSERT INTO available_permissions (id, name) + VALUES (@id, @name) + ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name;", conn)) + { + cmd.Parameters.AddWithValue("@id", perm.Id); + cmd.Parameters.AddWithValue("@name", perm.Name); + cmd.ExecuteNonQuery(); + } + } + + trans.Commit(); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error saving permissions to PostgreSQL: " + ex.Message); + } + } + + private List GetDefaultUsers() + { + return new List + { + new User + { + Username = "admin", + FullName = "System Administrator", + Password = PasswordHasher.HashPassword("admin"), + Permissions = new List { 1, 2, 3, 4, 5 } + }, + new User + { + Username = "user", + FullName = "Standard User", + Password = PasswordHasher.HashPassword("user"), + Permissions = new List { 1 } + } + }; + } + + private List GetDefaultPermissions() + { + return new List + { + new Permission { Id = 1, Name = "View Dashboard" }, + new Permission { Id = 2, Name = "Edit Settings" }, + new Permission { Id = 3, Name = "Manage Users" }, + new Permission { Id = 4, Name = "Full Control" }, + new Permission { Id = 5, Name = "Delete Transactions" } + }; + } + } +} diff --git a/UserPermissionTest_CS_WinForms/SessionManager.cs b/UserPermissionTest_CS_WinForms/SessionManager.cs index 90a3e0e..e071b9a 100644 --- a/UserPermissionTest_CS_WinForms/SessionManager.cs +++ b/UserPermissionTest_CS_WinForms/SessionManager.cs @@ -6,13 +6,13 @@ namespace UserPermissionTest_CS_WinForms { public static class SessionManager { - private static readonly IUserRepository UserRepository = new JsonUserRepository(); + private static readonly IUserRepository UserRepository = new PostgreSqlUserRepository("Host=127.0.0.1;Port=5432;Database=postgres;Username=postgres;Password=postgres"); public static User? CurrentUser { get; private set; } public static List Users { get; private set; } = new List(); - public static List AvailablePermissions { get; private set; } = new List(); + public static List AvailablePermissions { get; private set; } = new List(); public static event Action? SessionStateChanged; public static event Action? UsersUpdated; @@ -88,11 +88,13 @@ namespace UserPermissionTest_CS_WinForms } } - public static void AddPermission(string permission) + public static void AddPermission(string permissionName) { - if (!AvailablePermissions.Contains(permission)) + if (!AvailablePermissions.Any(p => p.Name.Equals(permissionName, StringComparison.OrdinalIgnoreCase))) { - AvailablePermissions.Add(permission); + int newId = AvailablePermissions.Count > 0 ? AvailablePermissions.Max(p => p.Id) + 1 : 1; + var newPerm = new Permission { Id = newId, Name = permissionName }; + AvailablePermissions.Add(newPerm); UserRepository.SavePermissions(AvailablePermissions); UsersUpdated?.Invoke(); } diff --git a/UserPermissionTest_CS_WinForms/User.cs b/UserPermissionTest_CS_WinForms/User.cs index e5e884d..dc7e740 100644 --- a/UserPermissionTest_CS_WinForms/User.cs +++ b/UserPermissionTest_CS_WinForms/User.cs @@ -8,7 +8,7 @@ namespace UserPermissionTest_CS_WinForms public string Username { get; set; } = string.Empty; public string FullName { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; - public List Permissions { get; set; } = new List(); + public List Permissions { get; set; } = new List(); public User Clone() { @@ -17,7 +17,7 @@ namespace UserPermissionTest_CS_WinForms Username = this.Username, FullName = this.FullName, Password = this.Password, - Permissions = new List(this.Permissions) + Permissions = new List(this.Permissions) }; } } diff --git a/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj b/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj index b42f4cb..7b4046a 100644 --- a/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj +++ b/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj @@ -11,6 +11,7 @@ + diff --git a/UserPermissionTest_CS_WinForms/UserSettings.cs b/UserPermissionTest_CS_WinForms/UserSettings.cs index 528d202..41daaff 100644 --- a/UserPermissionTest_CS_WinForms/UserSettings.cs +++ b/UserPermissionTest_CS_WinForms/UserSettings.cs @@ -77,16 +77,16 @@ namespace UserPermissionTest_CS_WinForms private void RefreshPermissionList() { - // Save currently checked permissions - var checkedPerms = new HashSet(); + // Save currently checked permission IDs + var checkedPermIds = new HashSet(); for (int i = 0; i < chkPermissions.Items.Count; i++) { if (chkPermissions.GetItemChecked(i)) { - string? itemText = chkPermissions.Items[i]?.ToString(); - if (itemText != null) + var permission = chkPermissions.Items[i] as Permission; + if (permission != null) { - checkedPerms.Add(itemText); + checkedPermIds.Add(permission.Id); } } } @@ -105,8 +105,8 @@ namespace UserPermissionTest_CS_WinForms { for (int i = 0; i < chkPermissions.Items.Count; i++) { - string? permissionName = chkPermissions.Items[i].ToString(); - if (permissionName != null && user.Permissions.Contains(permissionName)) + var permission = chkPermissions.Items[i] as Permission; + if (permission != null && user.Permissions.Contains(permission.Id)) { chkPermissions.SetItemChecked(i, true); } @@ -118,8 +118,8 @@ namespace UserPermissionTest_CS_WinForms // Otherwise, restore the checks that the administrator was in the middle of selecting for (int i = 0; i < chkPermissions.Items.Count; i++) { - string? permissionName = chkPermissions.Items[i].ToString(); - if (permissionName != null && checkedPerms.Contains(permissionName)) + var permission = chkPermissions.Items[i] as Permission; + if (permission != null && checkedPermIds.Contains(permission.Id)) { chkPermissions.SetItemChecked(i, true); } @@ -162,8 +162,8 @@ namespace UserPermissionTest_CS_WinForms // Update permissions CheckedListBox for (int i = 0; i < chkPermissions.Items.Count; i++) { - string? permissionName = chkPermissions.Items[i].ToString(); - bool hasPermission = permissionName != null && user.Permissions.Contains(permissionName); + var permission = chkPermissions.Items[i] as Permission; + bool hasPermission = permission != null && user.Permissions.Contains(permission.Id); chkPermissions.SetItemChecked(i, hasPermission); } @@ -334,17 +334,17 @@ namespace UserPermissionTest_CS_WinForms } } - private List GetCheckedPermissions() + private List GetCheckedPermissions() { - var list = new List(); + var list = new List(); for (int i = 0; i < chkPermissions.Items.Count; i++) { if (chkPermissions.GetItemChecked(i)) { - string? permissionName = chkPermissions.Items[i].ToString(); - if (permissionName != null) + var permission = chkPermissions.Items[i] as Permission; + if (permission != null) { - list.Add(permissionName); + list.Add(permission.Id); } } } @@ -361,7 +361,7 @@ namespace UserPermissionTest_CS_WinForms return; } - if (SessionManager.AvailablePermissions.Contains(newPerm, StringComparer.OrdinalIgnoreCase)) + if (SessionManager.AvailablePermissions.Any(p => p.Name.Equals(newPerm, StringComparison.OrdinalIgnoreCase))) { MessageBox.Show($"Permission '{newPerm}' already exists in the system.", "Duplicate Permission", MessageBoxButtons.OK, MessageBoxIcon.Warning); txtNewPermission.Focus(); diff --git a/winforms_security_sandbox.md b/winforms_security_sandbox.md index 993dec1..7056e61 100644 --- a/winforms_security_sandbox.md +++ b/winforms_security_sandbox.md @@ -43,7 +43,8 @@ Here are the key files created for this project in [UserPermissionTest_CS_WinFor * 📄 **[UserPermissionTest_CS_WinForms.csproj](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj)**: SDK-style project file configured for `net481` with Windows Forms enabled and `Newtonsoft.Json` package installed. * 📄 **[Program.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/Program.cs)**: Main execution entry point. -* 📄 **[User.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/User.cs)**: Object model for users holding Username, Full Name, Password (hashed), and Permissions list. +* 📄 **[User.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/User.cs)**: Object model for users holding Username, Full Name, Password (hashed), and Permissions list (ID-based, List). +* 📄 **[Permission.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/Permission.cs)**: Object model for system permissions holding Id (int) and Name (string). * 📄 **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)**: Dynamic state and session service utilizing cross-form event notifications (`SessionStateChanged`, `UsersUpdated`). * 📄 **[PasswordHasher.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/PasswordHasher.cs)**: Cryptographic utility providing one-way SHA-256 password hashing and validation. * 🖥 **MainForm**: @@ -58,9 +59,9 @@ Here are the key files created for this project in [UserPermissionTest_CS_WinFor --- -## 🗄️ Repository Pattern & PostgreSQL Migration Path +## 🗄️ Repository Pattern & PostgreSQL Database Integration -To address future scalability and facilitate a seamless transition to a relational database like **PostgreSQL**, the data layer has been decoupled using the **Repository Pattern**: +To address enterprise-level data persistence, the data layer has been decoupled using the **Repository Pattern** and is **fully integrated with PostgreSQL** using the `Npgsql` database driver: ```mermaid graph LR @@ -69,37 +70,21 @@ graph LR IUserRepository <|.. PostgreSqlUserRepository style IUserRepository fill:#1E293B,stroke:#F59E0B,stroke-width:2px,color:#fff - style JsonUserRepository fill:#1E293B,stroke:#10B981,stroke-width:2px - style PostgreSqlUserRepository fill:#1E293B,stroke:#0EA5E9,stroke-width:2px,stroke-dasharray: 5 5 + style JsonUserRepository fill:#1E293B,stroke:#10B981,stroke-width:2px,stroke-dasharray: 5 5 + style PostgreSqlUserRepository fill:#1E293B,stroke:#0EA5E9,stroke-width:2px ``` -### Decoupled Interfaces: +### Decoupled Implementations: * **[IUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/IUserRepository.cs)**: Contract layer. Declares `LoadUsers`, `SaveUsers`, `LoadPermissions`, and `SavePermissions`. -* **[JsonUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/JsonUserRepository.cs)**: Local file-based (JSON) storage provider using `Newtonsoft.Json`. Writes/reads database parameters to local files `users.json` / `permissions.json` inside output build directories. +* **[PostgreSqlUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs)**: **Active Production Provider**. Connects to a local PostgreSQL instance, handles automatic schema initialization (tables `users`, `user_permissions`, `available_permissions`), and uses robust atomic transactions for synchronization. +* **[JsonUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/JsonUserRepository.cs)**: Alternative file-based (JSON) storage provider using `Newtonsoft.Json`. Writes/reads database parameters to local files `users.json` / `permissions.json` inside output build directories. -### 🐘 PostgreSQL Migration Steps: -When you decide to migrate to PostgreSQL, you **only** need to follow these simple steps: -1. Install `Npgsql` (PostgreSQL Client) and a mapper like `EntityFramework` or `Dapper` via NuGet. -2. Implement a new class adhering to `IUserRepository`: - ```csharp - public class PostgreSqlUserRepository : IUserRepository - { - private readonly string _connectionString; - public PostgreSqlUserRepository(string connString) => _connectionString = connString; - - public List LoadUsers() { /* DB query */ } - public void SaveUsers(List users) { /* DB save */ } - public List LoadPermissions() { /* DB query */ } - public void SavePermissions(List permissions) { /* DB save */ } - } - ``` -3. Swap a single line in **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)**: - ```csharp - // Old: private static readonly IUserRepository UserRepository = new JsonUserRepository(); - // New: - private static readonly IUserRepository UserRepository = new PostgreSqlUserRepository("Host=localhost;Database=mydb;Username=postgres;Password=pwd"); - ``` -4. **Zero UI Code Modification**: None of the Windows Forms (`MainForm`, `LoginDialog`, `UserSettings`) will require any edits. The application will immediately pull from and write to PostgreSQL. +### 🐘 Active PostgreSQL Configuration: +The database connection is managed directly via Dependency Injection in **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)**: +```csharp +private static readonly IUserRepository UserRepository = new PostgreSqlUserRepository("Host=127.0.0.1;Port=5432;Database=postgres;Username=postgres;Password=postgres"); +``` +* **Seamless Interchangeability**: Switching back to JSON file storage is as simple as commenting out the PostgreSQL line and uncommenting the JSON class. None of the Windows Forms (`MainForm`, `LoginDialog`, `UserSettings`) require any code edits. --- @@ -203,7 +188,8 @@ graph TD * 📄 **[UserPermissionTest_CS_WinForms.csproj](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/UserPermissionTest_CS_WinForms.csproj)**: `net481` hedefleyen, Windows Forms etkinleştirilmiş ve `Newtonsoft.Json` bağımlılığı yüklenmiş SDK stili modern proje dosyası. * 📄 **[Program.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/Program.cs)**: Uygulamanın ana giriş noktası. -* 📄 **[User.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/User.cs)**: Kullanıcı adı, Ad Soyad, Parola (Hash formatında) ve İzin listesini barındıran veri sınıfı. +* 📄 **[User.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/User.cs)**: Kullanıcı adı, Ad Soyad, Parola (Hash formatında) ve İzin listesini (ID tabanlı, List) barındıran veri sınıfı. +* 📄 **[Permission.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/Permission.cs)**: Sistemdeki yetkilerin benzersiz kimliğini (Id) ve metnini (Name) barındıran veri sınıfı. * 📄 **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)**: Formlar arasında gerçek zamanlı olay bildirimleri (`SessionStateChanged`, `UsersUpdated`) sunan merkezi statik oturum yöneticisi. * 📄 **[PasswordHasher.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/PasswordHasher.cs)**: SHA-256 tek yönlü parola kriptolama ve doğrulama yardımcı sınıfı. * 🖥 **MainForm**: @@ -218,9 +204,9 @@ graph TD --- -## 🗄️ Depo Tasarım Deseni (Repository Pattern) & PostgreSQL Geçiş Yolu +## 🗄️ Depo Tasarım Deseni (Repository Pattern) & Aktif PostgreSQL Entegrasyonu -Gelecekte uygulamanın SQL veritabanına taşınmasını kolaylaştırmak ve ölçeklenebilirlik sağlamak adına veri erişim katmanı tamamen **Repository Deseni** ile soyutlaştırılmıştır: +Veri erişim katmanı tamamen **Repository Deseni** ile soyutlaştırılmış ve `Npgsql` veritabanı sürücüsü kullanılarak **aktif olarak PostgreSQL veritabanına bağlanmıştır**: ```mermaid graph LR @@ -229,37 +215,21 @@ graph LR IUserRepository <|.. PostgreSqlUserRepository style IUserRepository fill:#1E293B,stroke:#F59E0B,stroke-width:2px,color:#fff - style JsonUserRepository fill:#1E293B,stroke:#10B981,stroke-width:2px - style PostgreSqlUserRepository fill:#1E293B,stroke:#0EA5E9,stroke-width:2px,stroke-dasharray: 5 5 + style JsonUserRepository fill:#1E293B,stroke:#10B981,stroke-width:2px,stroke-dasharray: 5 5 + style PostgreSqlUserRepository fill:#1E293B,stroke:#0EA5E9,stroke-width:2px ``` -### Soyut Katmanlar: -* **[IUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/IUserRepository.cs)**: Tanım arayüzüdür. `LoadUsers`, `SaveUsers`, `LoadPermissions` ve `SavePermissions` metot imzalarını içerir. -* **[JsonUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/JsonUserRepository.cs)**: Dosya tabanlı (JSON) veri saklayıcıdır. Çıktı dizinindeki `users.json` ve `permissions.json` dosyalarını okur/yazar. +### Depo Gerçekleştirmeleri: +* **[IUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/IUserRepository.cs)**: Tanım arayüzüdür. `LoadUsers`, `SaveUsers`, `LoadPermissions` ve `SavePermissions` imzalarını içerir. +* **[PostgreSqlUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/PostgreSqlUserRepository.cs)**: **Aktif Veritabanı Sağlayıcısı**. PostgreSQL servisine bağlanır, sistem tablolarını (`users`, `user_permissions`, `available_permissions`) başlangıçta otomatik olarak oluşturur ve güvenli veritabanı işlemleri için `Transaction` tabanlı veri senkronizasyonu yapar. +* **[JsonUserRepository.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/JsonUserRepository.cs)**: Alternatif dosya tabanlı (JSON) veri saklayıcıdır. Çıktı dizinindeki `users.json` ve `permissions.json` dosyalarını okur/yazar. -### 🐘 PostgreSQL Geçiş Adımları: -Veritabanı kurulumunuzu yaptığınızda **yalnızca** şu basit adımları izlemeniz yeterli olacaktır: -1. `Npgsql` (PostgreSQL Client) ve `EntityFramework` veya `Dapper` kütüphanesini NuGet ile projenize yükleyin. -2. `IUserRepository` arayüzünü uygulayan yeni bir repository sınıfı yazın: - ```csharp - public class PostgreSqlUserRepository : IUserRepository - { - private readonly string _connectionString; - public PostgreSqlUserRepository(string connString) => _connectionString = connString; - - public List LoadUsers() { /* PostgreSQL SELECT sorgusu */ } - public void SaveUsers(List users) { /* PostgreSQL Kaydetme */ } - public List LoadPermissions() { /* DB yetki listesi sorgusu */ } - public void SavePermissions(List permissions) { /* DB yetki kaydı */ } - } - ``` -3. **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)** dosyasındaki tek satırı güncelleyin: - ```csharp - // Eski JSON: private static readonly IUserRepository UserRepository = new JsonUserRepository(); - // Yeni PostgreSQL: - private static readonly IUserRepository UserRepository = new PostgreSqlUserRepository("Host=localhost;Database=mydb;Username=postgres;Password=pwd"); - ``` -4. **Sıfır Arayüz Değişikliği**: `MainForm`, `LoginDialog` veya `UserSettings` formlarındaki tek bir satır kod dahi değiştirilmez. Uygulama verileri anında PostgreSQL üzerinden okuyup yazacaktır. +### 🐘 Aktif PostgreSQL Yapılandırması: +Veritabanı bağlantısı **[SessionManager.cs](file:///D:/02_Programming/CSharp/CodingSandbox/UserPermissionTest_CS_WinForms/SessionManager.cs)** dosyasında tek bir satır üzerinden Dependency Injection ile yönetilir: +```csharp +private static readonly IUserRepository UserRepository = new PostgreSqlUserRepository("Host=127.0.0.1;Port=5432;Database=postgres;Username=postgres;Password=postgres"); +``` +* **Kusursuz Değiştirilebilirlik**: JSON tabanlı dosya saklama modülüne geri geçmek isterseniz, PostgreSQL satırını yorum satırı yapıp JSON satırını açmanız yeterlidir. Arayüz kodlarında (`MainForm`, `LoginDialog` veya `UserSettings`) tek bir satır dahi değişmez. ---