Files
natsdotnet/src/NATS.Server/Auth/ClientPermissions.cs

166 lines
4.5 KiB
C#

using NATS.Server.Subscriptions;
namespace NATS.Server.Auth;
public sealed class ClientPermissions : IDisposable
{
private readonly PermissionSet? _publish;
private readonly PermissionSet? _subscribe;
private readonly ResponseTracker? _responseTracker;
private readonly PermissionLruCache _pubCache = new(128);
private ClientPermissions(PermissionSet? publish, PermissionSet? subscribe, ResponseTracker? responseTracker)
{
_publish = publish;
_subscribe = subscribe;
_responseTracker = responseTracker;
}
public static ClientPermissions? Build(Permissions? permissions)
{
if (permissions == null)
return null;
var pub = PermissionSet.Build(permissions.Publish);
var sub = PermissionSet.Build(permissions.Subscribe);
ResponseTracker? responseTracker = null;
if (permissions.Response != null)
responseTracker = new ResponseTracker(permissions.Response.MaxMsgs, permissions.Response.Expires);
if (pub == null && sub == null && responseTracker == null)
return null;
return new ClientPermissions(pub, sub, responseTracker);
}
public ResponseTracker? ResponseTracker => _responseTracker;
public bool IsPublishAllowed(string subject)
{
if (_publish == null)
return true;
if (_pubCache.TryGet(subject, out var cached))
return cached;
var allowed = _publish.IsAllowed(subject);
// If denied but response tracking is enabled, check reply table
if (!allowed && _responseTracker != null)
{
if (_responseTracker.IsReplyAllowed(subject))
return true; // Don't cache dynamic reply permissions
}
_pubCache.Set(subject, allowed);
return allowed;
}
public bool IsSubscribeAllowed(string subject, string? queue = null)
{
if (_subscribe == null)
return true;
if (!_subscribe.IsAllowed(subject))
return false;
if (queue != null && _subscribe.IsDenied(queue))
return false;
return true;
}
public bool IsDeliveryAllowed(string subject)
{
if (_subscribe == null)
return true;
return _subscribe.IsDeliveryAllowed(subject);
}
public void Dispose()
{
_publish?.Dispose();
_subscribe?.Dispose();
}
}
public sealed class PermissionSet : IDisposable
{
private readonly SubList? _allow;
private readonly SubList? _deny;
private PermissionSet(SubList? allow, SubList? deny)
{
_allow = allow;
_deny = deny;
}
public static PermissionSet? Build(SubjectPermission? permission)
{
if (permission == null)
return null;
bool hasAllow = permission.Allow is { Count: > 0 };
bool hasDeny = permission.Deny is { Count: > 0 };
if (!hasAllow && !hasDeny)
return null;
SubList? allow = null;
SubList? deny = null;
if (hasAllow)
{
allow = new SubList();
foreach (var subject in permission.Allow!)
allow.Insert(new Subscription { Subject = subject, Sid = "_perm_" });
}
if (hasDeny)
{
deny = new SubList();
foreach (var subject in permission.Deny!)
deny.Insert(new Subscription { Subject = subject, Sid = "_perm_" });
}
return new PermissionSet(allow, deny);
}
public bool IsAllowed(string subject)
{
bool allowed = true;
if (_allow != null)
{
var result = _allow.Match(subject);
allowed = result.PlainSubs.Length > 0 || result.QueueSubs.Length > 0;
}
if (allowed && _deny != null)
{
var result = _deny.Match(subject);
allowed = result.PlainSubs.Length == 0 && result.QueueSubs.Length == 0;
}
return allowed;
}
public bool IsDenied(string subject)
{
if (_deny == null) return false;
var result = _deny.Match(subject);
return result.PlainSubs.Length > 0 || result.QueueSubs.Length > 0;
}
public bool IsDeliveryAllowed(string subject)
{
if (_deny == null)
return true;
var result = _deny.Match(subject);
return result.PlainSubs.Length == 0 && result.QueueSubs.Length == 0;
}
public void Dispose()
{
_allow?.Dispose();
_deny?.Dispose();
}
}