using NATS.Server.Subscriptions; namespace NATS.Server.Auth; public sealed class ClientPermissions : IDisposable { private readonly PermissionSet? _publish; private readonly PermissionSet? _subscribe; private readonly PermissionLruCache _pubCache = new(128); private ClientPermissions(PermissionSet? publish, PermissionSet? subscribe) { _publish = publish; _subscribe = subscribe; } public static ClientPermissions? Build(Permissions? permissions) { if (permissions == null) return null; var pub = PermissionSet.Build(permissions.Publish); var sub = PermissionSet.Build(permissions.Subscribe); if (pub == null && sub == null) return null; return new ClientPermissions(pub, sub); } public bool IsPublishAllowed(string subject) { if (_publish == null) return true; if (_pubCache.TryGet(subject, out var cached)) return cached; var allowed = _publish.IsAllowed(subject); _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(); } }