feature: RBAC + Clinical Audit Logging
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
public class AuthorizePermissionAttribute : AuthorizeAttribute
|
||||
{
|
||||
public AuthorizePermissionAttribute(string permission)
|
||||
{
|
||||
Policy = $"perm:{permission}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
public static class ClinicalPermissions
|
||||
{
|
||||
public const string PatientsRead = "patients:read";
|
||||
public const string PatientsWrite = "patients:write";
|
||||
public const string EncountersRead = "encounters:read";
|
||||
public const string EncountersWrite = "encounters:write";
|
||||
public const string ObservationsIngest = "observations:ingest";
|
||||
public const string AlertsRead = "alerts:read";
|
||||
public const string AlertsAcknowledge = "alerts:acknowledge";
|
||||
public const string AlertsResolve = "alerts:resolve";
|
||||
public const string ThresholdsRead = "thresholds:read";
|
||||
public const string ThresholdsWrite = "thresholds:write";
|
||||
public const string AnalyticsRead = "analytics:read";
|
||||
public const string OrdersWrite = "orders:write";
|
||||
public const string MedicationsWrite = "medications:write";
|
||||
public const string FhirIngest = "fhir:ingest";
|
||||
public const string AuditRead = "audit:read";
|
||||
public const string UsersAdmin = "users:admin";
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
public static class ClinicalRolePermissionMap
|
||||
{
|
||||
private static readonly Dictionary<ClinicalRole, HashSet<string>> _map = new()
|
||||
{
|
||||
[ClinicalRole.Nurse] = new(StringComparer.Ordinal)
|
||||
{
|
||||
ClinicalPermissions.PatientsRead,
|
||||
ClinicalPermissions.EncountersRead,
|
||||
ClinicalPermissions.ObservationsIngest,
|
||||
ClinicalPermissions.AlertsRead,
|
||||
ClinicalPermissions.AlertsAcknowledge,
|
||||
ClinicalPermissions.AlertsResolve,
|
||||
ClinicalPermissions.ThresholdsRead,
|
||||
ClinicalPermissions.AnalyticsRead,
|
||||
ClinicalPermissions.OrdersWrite,
|
||||
ClinicalPermissions.MedicationsWrite,
|
||||
},
|
||||
[ClinicalRole.Physician] = new(StringComparer.Ordinal)
|
||||
{
|
||||
ClinicalPermissions.PatientsRead,
|
||||
ClinicalPermissions.EncountersRead,
|
||||
ClinicalPermissions.EncountersWrite,
|
||||
ClinicalPermissions.ObservationsIngest,
|
||||
ClinicalPermissions.AlertsRead,
|
||||
ClinicalPermissions.AlertsAcknowledge,
|
||||
ClinicalPermissions.AlertsResolve,
|
||||
ClinicalPermissions.ThresholdsRead,
|
||||
ClinicalPermissions.AnalyticsRead,
|
||||
ClinicalPermissions.OrdersWrite,
|
||||
ClinicalPermissions.MedicationsWrite,
|
||||
},
|
||||
[ClinicalRole.Admin] = new(StringComparer.Ordinal)
|
||||
{
|
||||
ClinicalPermissions.PatientsRead,
|
||||
ClinicalPermissions.PatientsWrite,
|
||||
ClinicalPermissions.EncountersRead,
|
||||
ClinicalPermissions.EncountersWrite,
|
||||
ClinicalPermissions.ObservationsIngest,
|
||||
ClinicalPermissions.AlertsRead,
|
||||
ClinicalPermissions.AlertsAcknowledge,
|
||||
ClinicalPermissions.AlertsResolve,
|
||||
ClinicalPermissions.ThresholdsRead,
|
||||
ClinicalPermissions.ThresholdsWrite,
|
||||
ClinicalPermissions.AnalyticsRead,
|
||||
ClinicalPermissions.OrdersWrite,
|
||||
ClinicalPermissions.MedicationsWrite,
|
||||
ClinicalPermissions.FhirIngest,
|
||||
ClinicalPermissions.AuditRead,
|
||||
ClinicalPermissions.UsersAdmin,
|
||||
},
|
||||
[ClinicalRole.Integration] = new(StringComparer.Ordinal)
|
||||
{
|
||||
ClinicalPermissions.PatientsWrite,
|
||||
ClinicalPermissions.EncountersWrite,
|
||||
ClinicalPermissions.ObservationsIngest,
|
||||
ClinicalPermissions.MedicationsWrite,
|
||||
ClinicalPermissions.FhirIngest,
|
||||
},
|
||||
};
|
||||
|
||||
public static bool HasPermission(ClinicalRole role, string permission) =>
|
||||
_map.TryGetValue(role, out var perms) && perms.Contains(permission);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
|
||||
{
|
||||
protected override Task HandleRequirementAsync(
|
||||
AuthorizationHandlerContext context,
|
||||
PermissionRequirement requirement)
|
||||
{
|
||||
var roleClaim = context.User.FindFirst("clinical_role")?.Value;
|
||||
if (roleClaim is null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var role = ClinicalRoleExtensions.FromDbString(roleClaim);
|
||||
if (ClinicalRolePermissionMap.HasPermission(role, requirement.Permission))
|
||||
context.Succeed(requirement);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
public class PermissionPolicyProvider : IAuthorizationPolicyProvider
|
||||
{
|
||||
private readonly DefaultAuthorizationPolicyProvider _fallback;
|
||||
|
||||
public PermissionPolicyProvider(IOptions<AuthorizationOptions> options)
|
||||
{
|
||||
_fallback = new DefaultAuthorizationPolicyProvider(options);
|
||||
}
|
||||
|
||||
public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
|
||||
{
|
||||
if (policyName.StartsWith("perm:", StringComparison.Ordinal))
|
||||
{
|
||||
var permission = policyName["perm:".Length..];
|
||||
var policy = new AuthorizationPolicyBuilder()
|
||||
.RequireAuthenticatedUser()
|
||||
.AddRequirements(new PermissionRequirement(permission))
|
||||
.Build();
|
||||
return Task.FromResult<AuthorizationPolicy?>(policy);
|
||||
}
|
||||
|
||||
return _fallback.GetPolicyAsync(policyName);
|
||||
}
|
||||
|
||||
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() =>
|
||||
_fallback.GetDefaultPolicyAsync();
|
||||
|
||||
public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() =>
|
||||
_fallback.GetFallbackPolicyAsync();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
public class PermissionRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public string Permission { get; }
|
||||
public PermissionRequirement(string permission) => Permission = permission;
|
||||
}
|
||||
Reference in New Issue
Block a user