445 lines
19 KiB
C#
445 lines
19 KiB
C#
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" }
|
|
};
|
|
}
|
|
}
|
|
}
|