added postgresql integration for user permissions

This commit is contained in:
Atakan Kayman
2026-05-31 15:54:46 +03:00
parent 374067dd2e
commit 7e18f84c42
10 changed files with 558 additions and 103 deletions
@@ -6,7 +6,7 @@ namespace UserPermissionTest_CS_WinForms
{
List<User> LoadUsers();
void SaveUsers(List<User> users);
List<string> LoadPermissions();
void SavePermissions(List<string> permissions);
List<Permission> LoadPermissions();
void SavePermissions(List<Permission> permissions);
}
}
@@ -74,7 +74,7 @@ namespace UserPermissionTest_CS_WinForms
}
}
public List<string> LoadPermissions()
public List<Permission> LoadPermissions()
{
if (!File.Exists(PermissionsFile))
{
@@ -86,7 +86,7 @@ namespace UserPermissionTest_CS_WinForms
try
{
string json = File.ReadAllText(PermissionsFile);
var permissions = JsonConvert.DeserializeObject<List<string>>(json);
var permissions = JsonConvert.DeserializeObject<List<Permission>>(json);
return permissions ?? GetDefaultPermissions();
}
catch (Exception ex)
@@ -96,7 +96,7 @@ namespace UserPermissionTest_CS_WinForms
}
}
public void SavePermissions(List<string> permissions)
public void SavePermissions(List<Permission> permissions)
{
try
{
@@ -118,27 +118,27 @@ namespace UserPermissionTest_CS_WinForms
Username = "admin",
FullName = "System Administrator",
Password = PasswordHasher.HashPassword("admin"),
Permissions = GetDefaultPermissions()
Permissions = new List<int> { 1, 2, 3, 4, 5 }
},
new User
{
Username = "user",
FullName = "Standard User",
Password = PasswordHasher.HashPassword("user"),
Permissions = new List<string> { "View Dashboard" }
Permissions = new List<int> { 1 }
}
};
}
private List<string> GetDefaultPermissions()
private List<Permission> GetDefaultPermissions()
{
return new List<string>
return new List<Permission>
{
"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" }
};
}
}
+18 -4
View File
@@ -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.",
@@ -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
};
}
}
}
@@ -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<User> LoadUsers()
{
var users = new List<User>();
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<int>()
});
}
}
// 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<User> 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<string>();
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<string>(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<Permission> LoadPermissions()
{
var permissions = new List<Permission>();
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<Permission> permissions)
{
try
{
using (var conn = new NpgsqlConnection(_connectionString))
{
conn.Open();
using (var trans = conn.BeginTransaction())
{
// Sync system permissions
var currentInDb = new List<int>();
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<int>(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<User> GetDefaultUsers()
{
return new List<User>
{
new User
{
Username = "admin",
FullName = "System Administrator",
Password = PasswordHasher.HashPassword("admin"),
Permissions = new List<int> { 1, 2, 3, 4, 5 }
},
new User
{
Username = "user",
FullName = "Standard User",
Password = PasswordHasher.HashPassword("user"),
Permissions = new List<int> { 1 }
}
};
}
private List<Permission> GetDefaultPermissions()
{
return new List<Permission>
{
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" }
};
}
}
}
@@ -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<User> Users { get; private set; } = new List<User>();
public static List<string> AvailablePermissions { get; private set; } = new List<string>();
public static List<Permission> AvailablePermissions { get; private set; } = new List<Permission>();
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();
}
+2 -2
View File
@@ -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<string> Permissions { get; set; } = new List<string>();
public List<int> Permissions { get; set; } = new List<int>();
public User Clone()
{
@@ -17,7 +17,7 @@ namespace UserPermissionTest_CS_WinForms
Username = this.Username,
FullName = this.FullName,
Password = this.Password,
Permissions = new List<string>(this.Permissions)
Permissions = new List<int>(this.Permissions)
};
}
}
@@ -11,6 +11,7 @@
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Npgsql" Version="8.0.9" />
</ItemGroup>
</Project>
+17 -17
View File
@@ -77,16 +77,16 @@ namespace UserPermissionTest_CS_WinForms
private void RefreshPermissionList()
{
// Save currently checked permissions
var checkedPerms = new HashSet<string>();
// Save currently checked permission IDs
var checkedPermIds = new HashSet<int>();
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<string> GetCheckedPermissions()
private List<int> GetCheckedPermissions()
{
var list = new List<string>();
var list = new List<int>();
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();