mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-30 17:30:40 +00:00
Managers
This commit is contained in:
@@ -5,7 +5,8 @@ namespace Esiur.Security.Management;
|
||||
/// <summary>
|
||||
/// Evaluates whether a resource operation is admitted by rate-control policy.
|
||||
/// A manager may assign <see cref="ResourceManagerContext.Delay"/> when allowing
|
||||
/// an operation that should be queued.
|
||||
/// an operation that should be queued and
|
||||
/// <see cref="ResourceManagerContext.SupportsDelay"/> is true.
|
||||
/// </summary>
|
||||
public interface IRateControlManager : IResourceManager
|
||||
{
|
||||
|
||||
@@ -10,9 +10,9 @@ using System.Linq;
|
||||
namespace Esiur.Security.Management;
|
||||
|
||||
/// <summary>
|
||||
/// Immutable metadata describing a resource operation being evaluated by managers.
|
||||
/// <see cref="Delay"/> is the sole mutable value and allows rate-control managers to
|
||||
/// request deferred execution.
|
||||
/// Metadata describing a resource operation being evaluated by managers. Operation
|
||||
/// identity is immutable; <see cref="Delay"/> and <see cref="DenialReason"/> let a
|
||||
/// manager return admission details.
|
||||
/// </summary>
|
||||
public sealed class ResourceManagerContext
|
||||
{
|
||||
@@ -23,9 +23,16 @@ public sealed class ResourceManagerContext
|
||||
public Session? Session { get; }
|
||||
public IResource? Resource { get; }
|
||||
public MemberDef? Member { get; }
|
||||
public TypeDef? TypeDefinition { get; }
|
||||
public ActionType Action { get; }
|
||||
public object? Inquirer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the protocol operation can honor an allowed delayed ruling.
|
||||
/// Rate managers should inspect this before reserving queued capacity.
|
||||
/// </summary>
|
||||
public bool SupportsDelay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local attributes that configure manager policy for the target member.
|
||||
/// The collection is a defensive, read-only snapshot.
|
||||
@@ -33,10 +40,17 @@ public sealed class ResourceManagerContext
|
||||
public IReadOnlyList<Attribute> MemberPolicyAttributes => _memberPolicyAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional delay requested by a rate-control manager.
|
||||
/// Gets or sets an optional delay requested by a rate-control manager. Execute,
|
||||
/// property-set, and pull-stream operations honor delayed rulings; operations
|
||||
/// that cannot be delayed fail closed.
|
||||
/// </summary>
|
||||
public TimeSpan Delay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional public-safe reason supplied by a manager when it denies an operation.
|
||||
/// </summary>
|
||||
public string? DenialReason { get; set; }
|
||||
|
||||
public ResourceManagerContext(
|
||||
Warehouse warehouse,
|
||||
EpConnection? connection,
|
||||
@@ -45,15 +59,19 @@ public sealed class ResourceManagerContext
|
||||
MemberDef? member,
|
||||
ActionType action,
|
||||
object? inquirer = null,
|
||||
IEnumerable<Attribute>? memberPolicyAttributes = null)
|
||||
IEnumerable<Attribute>? memberPolicyAttributes = null,
|
||||
TypeDef? typeDefinition = null,
|
||||
bool supportsDelay = false)
|
||||
{
|
||||
Warehouse = warehouse ?? throw new ArgumentNullException(nameof(warehouse));
|
||||
Connection = connection;
|
||||
Session = session;
|
||||
Resource = resource;
|
||||
Member = member;
|
||||
TypeDefinition = typeDefinition ?? resource?.Instance?.Definition ?? member?.Definition;
|
||||
Action = action;
|
||||
Inquirer = inquirer;
|
||||
SupportsDelay = supportsDelay;
|
||||
|
||||
var policies = memberPolicyAttributes?.ToArray() ?? Array.Empty<Attribute>();
|
||||
if (policies.Any(attribute => attribute == null))
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Esiur.Security.Permissions;
|
||||
using System;
|
||||
|
||||
namespace Esiur.Security.Management;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregated decisions from each independent manager category.
|
||||
/// An allow in one category never grants admission in another category.
|
||||
/// </summary>
|
||||
public sealed class ResourceManagerEvaluation
|
||||
{
|
||||
public Ruling Permissions { get; }
|
||||
public Ruling RateControl { get; }
|
||||
public Ruling Auditing { get; }
|
||||
public TimeSpan Delay { get; }
|
||||
|
||||
public string? PermissionsDenialReason { get; }
|
||||
public string? RateControlDenialReason { get; }
|
||||
public string? AuditingDenialReason { get; }
|
||||
|
||||
public bool IsAllowed =>
|
||||
Permissions == Ruling.Allowed &&
|
||||
RateControl != Ruling.Denied &&
|
||||
Auditing != Ruling.Denied;
|
||||
|
||||
internal ResourceManagerEvaluation(
|
||||
Ruling permissions,
|
||||
Ruling rateControl,
|
||||
Ruling auditing,
|
||||
TimeSpan delay,
|
||||
string? permissionsDenialReason,
|
||||
string? rateControlDenialReason,
|
||||
string? auditingDenialReason)
|
||||
{
|
||||
Permissions = permissions;
|
||||
RateControl = rateControl;
|
||||
Auditing = auditing;
|
||||
Delay = delay > TimeSpan.Zero ? delay : TimeSpan.Zero;
|
||||
PermissionsDenialReason = permissionsDenialReason;
|
||||
RateControlDenialReason = rateControlDenialReason;
|
||||
AuditingDenialReason = auditingDenialReason;
|
||||
}
|
||||
}
|
||||
@@ -46,5 +46,12 @@ public enum ActionType
|
||||
RemoveChild,
|
||||
Rename,
|
||||
ReceiveEvent,
|
||||
ViewTypeDef
|
||||
ViewTypeDef,
|
||||
Detach,
|
||||
Subscribe,
|
||||
Unsubscribe,
|
||||
PullStream,
|
||||
TerminateExecution,
|
||||
HaltExecution,
|
||||
ResumeExecution
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ using Esiur.Core;
|
||||
using Esiur.Resource;
|
||||
using Esiur.Security.Authority;
|
||||
using Esiur.Data.Types;
|
||||
using System.Linq;
|
||||
|
||||
namespace Esiur.Security.Permissions;
|
||||
|
||||
@@ -41,7 +42,29 @@ public class StorePermissionsManager : IPermissionsManager
|
||||
|
||||
public Ruling Applicable(IResource resource, Session session, ActionType action, MemberDef member, object inquirer = null)
|
||||
{
|
||||
return resource.Instance.Store.Instance.Applicable(session, action, member, inquirer);
|
||||
var storeInstance = resource?.Instance?.Store?.Instance;
|
||||
if (storeInstance == null)
|
||||
return Ruling.DontCare;
|
||||
|
||||
// Delegate only to managers attached to the store. Re-entering
|
||||
// Instance.Applicable would run Warehouse defaults (including this manager)
|
||||
// again and can recurse indefinitely or charge rate policies twice.
|
||||
var store = storeInstance.Resource;
|
||||
var allowed = false;
|
||||
|
||||
foreach (var manager in storeInstance.Managers
|
||||
.ToArray()
|
||||
.OfType<IPermissionsManager>()
|
||||
.Where(manager => !ReferenceEquals(manager, this)))
|
||||
{
|
||||
var ruling = manager.Applicable(store, session, action, member, inquirer);
|
||||
if (ruling == Ruling.Denied)
|
||||
return Ruling.Denied;
|
||||
if (ruling == Ruling.Allowed)
|
||||
allowed = true;
|
||||
}
|
||||
|
||||
return allowed ? Ruling.Allowed : Ruling.DontCare;
|
||||
}
|
||||
|
||||
public bool Initialize(Map<string,object> settings, IResource resource)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Esiur.Security.Management;
|
||||
using Esiur.Security.Permissions;
|
||||
using System;
|
||||
|
||||
namespace Esiur.Security.RateLimiting;
|
||||
|
||||
/// <summary>
|
||||
/// Bridges the existing named RateControl policy registry into the unified
|
||||
/// resource-manager pipeline.
|
||||
/// </summary>
|
||||
internal sealed class NamedRateControlManager : IRateControlManager
|
||||
{
|
||||
public Ruling Applicable(ResourceManagerContext context)
|
||||
{
|
||||
if (context.Action != ActionType.Execute &&
|
||||
context.Action != ActionType.SetProperty)
|
||||
return Ruling.DontCare;
|
||||
|
||||
var policyName = context.Member?.RatePolicyName;
|
||||
if (string.IsNullOrWhiteSpace(policyName))
|
||||
return Ruling.DontCare;
|
||||
|
||||
if (context.Connection is null || context.Session is null || context.Member is null)
|
||||
{
|
||||
context.DenialReason = $"Rate policy `{policyName}` requires an authenticated connection.";
|
||||
return Ruling.Denied;
|
||||
}
|
||||
|
||||
var policy = context.Warehouse.TryGetRatePolicy(policyName);
|
||||
if (policy is null)
|
||||
{
|
||||
context.DenialReason = $"Rate policy `{policyName}` is not registered.";
|
||||
return Ruling.Denied;
|
||||
}
|
||||
|
||||
var rateContext = new RateControlContext(
|
||||
context.Warehouse,
|
||||
context.Connection,
|
||||
context.Session,
|
||||
context.Resource,
|
||||
context.Member,
|
||||
context.Action);
|
||||
|
||||
var ruling = policy.Applicable(rateContext);
|
||||
if (rateContext.Delay > context.Delay)
|
||||
context.Delay = rateContext.Delay;
|
||||
|
||||
if (ruling == Ruling.Denied && string.IsNullOrWhiteSpace(context.DenialReason))
|
||||
context.DenialReason = $"Rate policy `{policyName}` denied `{context.Member.Fullname}`.";
|
||||
|
||||
return ruling;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user