feat: Add Admin Groups Management Module
- Introduced a new module for managing user groups (`usrgrp`) in the administration interface. - Implemented CRUD operations for user groups: list, create, rename, and delete. - Added authorization checks to ensure only users with appropriate permissions can manage groups. - Created corresponding frontend JavaScript and SCSS files for the new module. - Updated documentation to reflect the new group management functionality and its authorization model. - Added SQL stored procedures for group management operations. - Established a new login script for database access, ensuring proper security practices.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OCORE.SQL;
|
||||
using static OCORE.commons;
|
||||
using static OCORE.OCORE_dictionaries;
|
||||
using static OCORE.SQL.sql;
|
||||
using static OCORE.web.mvc_helper_async;
|
||||
|
||||
namespace Fuchs.Controllers;
|
||||
|
||||
// Partial class: Administration user-group management module (fn=usrgrp).
|
||||
//
|
||||
// Split out as its own module (rather than folded into the usr module) per
|
||||
// Docs/Decisions/0021-user-group-management.md, following the pattern
|
||||
// Docs/Decisions/0015-split-admin-user-management-module.md already established for adding a new
|
||||
// admin-only capability. Shares the Admin/usr modules' fds_sys > 4 gate (AdminMinAuthExclusive,
|
||||
// GetSystemAdminAuthAsync, AdminJson — all in IntranetController.Admin.cs).
|
||||
//
|
||||
// The fis_admin_* stored procs/functions called below enforce the SAME SEPARATE, independent
|
||||
// authorization axis as the usr module: the per-user "intranet_admin" setting via
|
||||
// fis_admin_getUserAuth(@authaccount) > 1. An account with fds_sys > 4 but no intranet_admin
|
||||
// setting will reach these endpoints yet get empty results / no-op writes from SQL — accounts
|
||||
// doing group management need BOTH.
|
||||
public partial class IntranetController
|
||||
{
|
||||
private async Task<IActionResult> Do_Process_AdminGroups(string fn, string id, string code)
|
||||
{
|
||||
_logger.LogDebug("Do_Process_AdminGroups action={Action} code={Code} user={User}", id, code, UserAccountID);
|
||||
|
||||
int authLevel = await GetSystemAdminAuthAsync(fn, id, code);
|
||||
bool authorized = authLevel > AdminMinAuthExclusive;
|
||||
|
||||
if (id.Equals("auth", StringComparison.OrdinalIgnoreCase))
|
||||
return await JSONAsync(new { manage = authorized ? 1 : 0, level = authLevel });
|
||||
|
||||
if (!authorized)
|
||||
{
|
||||
_logger.LogWarning("AdminGroups access denied for user={User} (fds_sys={Level}) action={Action}",
|
||||
UserAccountID, authLevel, id);
|
||||
return Unauthorized401();
|
||||
}
|
||||
|
||||
switch (id.ToLowerInvariant())
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
var res = await getSQLDatatable_async(
|
||||
"SELECT * FROM [dbo].[fis_admin_getUserGroupList](@authaccount, NULL) ORDER BY [order];",
|
||||
_intranet.Intranet__SQLConnectionString,
|
||||
StdParamlist(SQL_VarChar("@authaccount", UserAccountID)),
|
||||
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||
return AdminJson(new { groups = res.DataTable.toArrayofObjectDictionaries() });
|
||||
}
|
||||
|
||||
case "create":
|
||||
case "rename":
|
||||
{
|
||||
if (!HasForm("name")) return BadRequest400();
|
||||
string name = Form("name");
|
||||
string? groupId = id.Equals("rename", StringComparison.OrdinalIgnoreCase) ? Form("usergroup_id") : null;
|
||||
if (id.Equals("rename", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(groupId))
|
||||
return BadRequest400();
|
||||
|
||||
var pl = StdParamlist(
|
||||
SQL_VarChar("@authaccount", UserAccountID),
|
||||
SQL_VarChar("@usergroup_id", groupId, dbNull_IfEmpty: true),
|
||||
SQL_NVarChar("@name", name));
|
||||
|
||||
System.Data.DataRow? savedRow;
|
||||
try
|
||||
{
|
||||
var res = await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fis_admin_setUserGroup] @authaccount, @usergroup_id, @name, @enc_key;",
|
||||
_intranet.Intranet__SQLConnectionString, pl,
|
||||
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||
savedRow = res.Count > 0 ? res.FirstRow : null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyAdminUserActionFailedAsync("Gruppe speichern fehlgeschlagen",
|
||||
"Eine Benutzergruppe konnte nicht gespeichert werden.", groupId ?? "", ex);
|
||||
return StatusCode(500, new { error = "save_failed" });
|
||||
}
|
||||
|
||||
if (savedRow == null)
|
||||
{
|
||||
_logger.LogWarning("AdminGroups {Action} not successful group={Group} user={User}", id, groupId, UserAccountID);
|
||||
return StatusCode(500, new { error = "save_failed" });
|
||||
}
|
||||
|
||||
_logger.LogInformation("AdminGroups {Action} succeeded group={Group} user={User}",
|
||||
id, savedRow.nz("usergroup_id"), UserAccountID);
|
||||
return AdminJson(new { ok = true, usergroup_id = savedRow.nz("usergroup_id"), name = savedRow.nz("name") });
|
||||
}
|
||||
|
||||
case "delete":
|
||||
{
|
||||
if (!HasForm("usergroup_id")) return BadRequest400();
|
||||
string groupId = Form("usergroup_id");
|
||||
|
||||
bool success;
|
||||
try
|
||||
{
|
||||
var res = await getSQLDatatable_async(
|
||||
"EXECUTE [dbo].[fis_admin_delUserGroup] @authaccount, @usergroup_id;",
|
||||
_intranet.Intranet__SQLConnectionString,
|
||||
StdParamlist(SQL_VarChar("@authaccount", UserAccountID), SQL_VarChar("@usergroup_id", groupId)),
|
||||
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||
success = res.Count > 0 && res.FirstRow[0] is true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await NotifyAdminUserActionFailedAsync("Gruppe löschen fehlgeschlagen",
|
||||
$"Die Benutzergruppe {groupId} konnte nicht gelöscht werden.", groupId, ex);
|
||||
return StatusCode(500, new { error = "delete_failed" });
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
_logger.LogWarning("AdminGroups delete not successful group={Group} user={User}", groupId, UserAccountID);
|
||||
return StatusCode(500, new { error = "delete_failed" });
|
||||
}
|
||||
|
||||
_logger.LogInformation("AdminGroups delete succeeded group={Group} user={User}", groupId, UserAccountID);
|
||||
return AdminJson(new { ok = true });
|
||||
}
|
||||
|
||||
default:
|
||||
_logger.LogWarning("AdminGroups: no handler for action={Action}, user={User}", id, UserAccountID);
|
||||
return BadRequest400();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Web;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using OCORE.SQL;
|
||||
@@ -44,10 +44,16 @@ public partial class IntranetController
|
||||
{
|
||||
case "list":
|
||||
{
|
||||
// Optional useraccount_id narrows the result to a single account so the frontend can
|
||||
// refresh one row in place (e.g. after an email or group-membership change) instead
|
||||
// of re-rendering the whole grid and losing the current selection.
|
||||
string? filterId = HasForm("useraccount_id") ? Form("useraccount_id") : null;
|
||||
var res = await getSQLDatatable_async(
|
||||
"SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, NULL, NULL, NULL) ORDER BY [order];",
|
||||
"SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, @useraccount_id, NULL, NULL) ORDER BY [order];",
|
||||
_intranet.Intranet__SQLConnectionString,
|
||||
StdParamlist(SQL_VarChar("@authaccount", UserAccountID)),
|
||||
StdParamlist(
|
||||
SQL_VarChar("@authaccount", UserAccountID),
|
||||
SQL_VarChar("@useraccount_id", filterId, dbNull_IfEmpty: true)),
|
||||
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||
return AdminJson(new { users = res.DataTable.toArrayofObjectDictionaries() });
|
||||
}
|
||||
|
||||
@@ -168,6 +168,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
|
||||
"bam" => await Do_Process_Bankings(fn, id, code),
|
||||
"admin" => await Do_Process_Admin(fn, id, code),
|
||||
"usr" => await Do_Process_AdminUsers(fn, id, code),
|
||||
"usrgrp" => await Do_Process_AdminGroups(fn, id, code),
|
||||
"auth" => await HandleAuth(fn, id, code),
|
||||
"spwc" => await HandleSendPasswordCode(fn, id, code),
|
||||
"spw" => await HandleSendPassword(fn, id, code),
|
||||
|
||||
Reference in New Issue
Block a user