diff options
-rw-r--r-- | lib/Plugins.Essentials/src/Accounts/AccountUtils.cs | 47 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Endpoints/ProtectionSettings.cs | 43 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs | 15 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/EventProcessor.cs | 2 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/FileProcessArgs.cs | 80 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/TimestampedCounter.cs | 95 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryHandle.cs | 38 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryHandleTest.cs | 38 |
8 files changed, 164 insertions, 194 deletions
diff --git a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs index 75c3388..13cff7b 100644 --- a/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs +++ b/lib/Plugins.Essentials/src/Accounts/AccountUtils.cs @@ -308,7 +308,7 @@ namespace VNLib.Plugins.Essentials.Accounts IClientAuthorization auth = provider.AuthorizeClient(entity, secInfo, user); //Clear flags - user.FailedLoginCount(0); + user.ClearFailedLoginCount(); //Store variables entity.Session.UserID = user.UserID; @@ -526,20 +526,42 @@ namespace VNLib.Plugins.Essentials.Accounts public static TimestampedCounter FailedLoginCount(this IUser user) { ulong value = user.GetValueType<string, ulong>(FAILED_LOGIN_ENTRY); - return (TimestampedCounter)value; + return TimestampedCounter.FromUInt64(value); } + + /// <summary> + /// Clears any pending flc count. + /// </summary> + /// <param name="user"></param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ClearFailedLoginCount(this IUser user) + { + //Cast the counter to a ulong and store as a ulong + user.SetValueType(FAILED_LOGIN_ENTRY, (ulong)0); + } + /// <summary> /// Sets the number of failed login attempts for the current session /// </summary> /// <param name="user"></param> + /// <param name="time">Explicitly sets the time of the internal counter</param> /// <param name="value">The value to set the failed login attempt count</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FailedLoginCount(this IUser user, uint value) + public static void FailedLoginCount(this IUser user, uint value, DateTimeOffset time) { - TimestampedCounter counter = new(value); + TimestampedCounter counter = TimestampedCounter.FromValues(value, time); //Cast the counter to a ulong and store as a ulong - user.SetValueType(FAILED_LOGIN_ENTRY, (ulong)counter); + user.SetValueType(FAILED_LOGIN_ENTRY, counter.ToUInt64()); } + + /// <summary> + /// Sets the number of failed login attempts for the current session + /// </summary> + /// <param name="user"></param> + /// <param name="value">The value to set the failed login attempt count</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void FailedLoginCount(this IUser user, uint value) => FailedLoginCount(user, value, DateTimeOffset.UtcNow); + /// <summary> /// Sets the number of failed login attempts for the current session /// </summary> @@ -549,17 +571,26 @@ namespace VNLib.Plugins.Essentials.Accounts public static void FailedLoginCount(this IUser user, TimestampedCounter value) { //Cast the counter to a ulong and store as a ulong - user.SetValueType(FAILED_LOGIN_ENTRY, (ulong)value); + user.SetValueType(FAILED_LOGIN_ENTRY, value.ToUInt64()); } + + /// <summary> + /// Increments the failed login attempt count + /// </summary> + /// <param name="user"></param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void FailedLoginIncrement(this IUser user) => FailedLoginIncrement(user, DateTimeOffset.UtcNow); + /// <summary> /// Increments the failed login attempt count /// </summary> /// <param name="user"></param> + /// <param name="time">Explicitly set the time of the counter</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void FailedLoginIncrement(this IUser user) + public static void FailedLoginIncrement(this IUser user, DateTimeOffset time) { TimestampedCounter current = user.FailedLoginCount(); - user.FailedLoginCount(current.Count + 1); + user.FailedLoginCount(current.Count + 1, time); } #endregion diff --git a/lib/Plugins.Essentials/src/Endpoints/ProtectionSettings.cs b/lib/Plugins.Essentials/src/Endpoints/ProtectionSettings.cs index 77620ac..7e1e394 100644 --- a/lib/Plugins.Essentials/src/Endpoints/ProtectionSettings.cs +++ b/lib/Plugins.Essentials/src/Endpoints/ProtectionSettings.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -31,7 +31,7 @@ namespace VNLib.Plugins.Essentials.Endpoints /// for connection pre-checks. Settings are the most /// strict by default /// </summary> - public readonly struct ProtectionSettings : IEquatable<ProtectionSettings> + public readonly record struct ProtectionSettings { /// <summary> /// Requires TLS be enabled for all incomming requets (or loopback adapter) @@ -45,12 +45,6 @@ namespace VNLib.Plugins.Essentials.Endpoints public readonly bool DisableSessionsRequired { get; init; } /// <summary> - /// Allows connections that define cross-site sec headers - /// to be processed or denied (denied by default) - /// </summary> - public readonly bool DisableCrossSiteDenied { get; init; } - - /// <summary> /// Enables referr match protection. Requires that if a referer header is /// set that it matches the current origin /// </summary> @@ -64,40 +58,9 @@ namespace VNLib.Plugins.Essentials.Endpoints public readonly bool DisableBrowsersOnly { get; init; } /// <summary> - /// If the connection has a valid session, verifies that the - /// stored session origin matches the client's origin header. - /// (confirms the session is coming from the same origin it - /// was created on) - /// </summary> - public readonly bool DisableVerifySessionCors { get; init; } - - /// <summary> /// Disables response caching, by setting the cache control headers appropriatly. /// Default is disabled /// </summary> - public readonly bool EnableCaching { get; init; } - - - ///<inheritdoc/> - public override bool Equals(object obj) => obj is ProtectionSettings settings && Equals(settings); - ///<inheritdoc/> - public override int GetHashCode() => base.GetHashCode(); - - ///<inheritdoc/> - public static bool operator ==(ProtectionSettings left, ProtectionSettings right) => left.Equals(right); - ///<inheritdoc/> - public static bool operator !=(ProtectionSettings left, ProtectionSettings right) => !(left == right); - - ///<inheritdoc/> - public bool Equals(ProtectionSettings other) - { - return DisabledTlsRequired == other.DisabledTlsRequired && - DisableSessionsRequired == other.DisableSessionsRequired && - DisableCrossSiteDenied == other.DisableCrossSiteDenied && - DisableRefererMatch == other.DisableRefererMatch && - DisableBrowsersOnly == other.DisableBrowsersOnly && - DisableVerifySessionCors == other.DisableVerifySessionCors && - EnableCaching == other.EnableCaching; - } + public readonly bool EnableCaching { get; init; } } } diff --git a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs index 4af3c30..7411789 100644 --- a/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs +++ b/lib/Plugins.Essentials/src/Endpoints/ResourceEndpointBase.cs @@ -174,21 +174,6 @@ namespace VNLib.Plugins.Essentials.Endpoints return false; } - /* - * If sessions are required, verify cors is set, and the client supplied an origin header, - * verify that it matches the origin that was specified during session initialization - */ - if ((!EndpointProtectionSettings.DisableSessionsRequired & !EndpointProtectionSettings.DisableVerifySessionCors) && entity.Server.Origin != null && !entity.Session.CrossOriginMatch) - { - return false; - } - - //Enforce cross-site - if (!EndpointProtectionSettings.DisableCrossSiteDenied && entity.Server.IsCrossSite()) - { - return false; - } - return true; } diff --git a/lib/Plugins.Essentials/src/EventProcessor.cs b/lib/Plugins.Essentials/src/EventProcessor.cs index ccaa1a1..820af30 100644 --- a/lib/Plugins.Essentials/src/EventProcessor.cs +++ b/lib/Plugins.Essentials/src/EventProcessor.cs @@ -513,7 +513,7 @@ namespace VNLib.Plugins.Essentials //See if the last modifed header was set DateTimeOffset? ifModifedSince = entity.Server.LastModified(); //If the header was set, check the date, if the file has been modified since, continue sending the file - if (ifModifedSince != null && ifModifedSince.Value > fileLastModified) + if (ifModifedSince.HasValue && ifModifedSince.Value > fileLastModified) { //File has not been modified entity.CloseResponse(HttpStatusCode.NotModified); diff --git a/lib/Plugins.Essentials/src/FileProcessArgs.cs b/lib/Plugins.Essentials/src/FileProcessArgs.cs index dae695b..5ed4667 100644 --- a/lib/Plugins.Essentials/src/FileProcessArgs.cs +++ b/lib/Plugins.Essentials/src/FileProcessArgs.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -25,6 +25,8 @@ using System; using System.Net; +#nullable enable + namespace VNLib.Plugins.Essentials { /// <summary> @@ -69,7 +71,13 @@ namespace VNLib.Plugins.Essentials /// <summary> /// Specifies operations the file processor will follow during request handling /// </summary> - public readonly struct FileProcessArgs : IEquatable<FileProcessArgs> + /// <param name="Alternate"> + /// The routine the file processor should execute + /// </param> + /// <param name="Routine"> + /// An optional alternate path for the given routine + /// </param> + public readonly record struct FileProcessArgs(FpRoutine Routine, string Alternate) { /// <summary> /// Signals the file processor should complete with a <see cref="FpRoutine.Deny"/> routine @@ -91,79 +99,15 @@ namespace VNLib.Plugins.Essentials /// Signals the file processor should not process the connection /// </summary> public static readonly FileProcessArgs VirtualSkip = new (FpRoutine.VirtualSkip); - /// <summary> - /// The routine the file processor should execute - /// </summary> - public readonly FpRoutine Routine { get; init; } - /// <summary> - /// An optional alternate path for the given routine - /// </summary> - public readonly string Alternate { get; init; } + /// <summary> /// Initializes a new <see cref="FileProcessArgs"/> with the specified routine /// and empty <see cref="Alternate"/> path /// </summary> /// <param name="routine">The file processing routine to execute</param> - public FileProcessArgs(FpRoutine routine) - { - this.Routine = routine; - this.Alternate = string.Empty; - } - /// <summary> - /// Initializes a new <see cref="FileProcessArgs"/> with the specified routine - /// and alternate path - /// </summary> - /// <param name="routine"></param> - /// <param name="alternatePath"></param> - public FileProcessArgs(FpRoutine routine, string alternatePath) - { - this.Routine = routine; - this.Alternate = alternatePath; - } - /// <summary> - /// - /// </summary> - /// <param name="arg1"></param> - /// <param name="arg2"></param> - /// <returns></returns> - public static bool operator == (FileProcessArgs arg1, FileProcessArgs arg2) - { - return arg1.Equals(arg2); - } - /// <summary> - /// - /// </summary> - /// <param name="arg1"></param> - /// <param name="arg2"></param> - /// <returns></returns> - public static bool operator != (FileProcessArgs arg1, FileProcessArgs arg2) - { - return !arg1.Equals(arg2); - } - ///<inheritdoc/> - public bool Equals(FileProcessArgs other) - { - //make sure the routine types match - if (Routine != other.Routine) - { - return false; - } - //Next make sure the hashcodes of the alternate paths match - return (Alternate?.GetHashCode(StringComparison.OrdinalIgnoreCase)) == (other.Alternate?.GetHashCode(StringComparison.OrdinalIgnoreCase)); - } - ///<inheritdoc/> - public override bool Equals(object obj) - { - return obj is FileProcessArgs args && Equals(args); - } - /// <summary> - /// <inheritdoc/> - /// </summary> - /// <returns></returns> - public override int GetHashCode() + public FileProcessArgs(FpRoutine routine):this(routine, string.Empty) { - return base.GetHashCode(); } } }
\ No newline at end of file diff --git a/lib/Plugins.Essentials/src/TimestampedCounter.cs b/lib/Plugins.Essentials/src/TimestampedCounter.cs index 19cb8ec..b2ad70c 100644 --- a/lib/Plugins.Essentials/src/TimestampedCounter.cs +++ b/lib/Plugins.Essentials/src/TimestampedCounter.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -24,56 +24,32 @@ using System; - -#nullable enable - namespace VNLib.Plugins.Essentials { + /// <summary> /// Stucture that allows for convient storage of a counter value /// and a second precision timestamp into a 64-bit unsigned integer /// </summary> - public readonly struct TimestampedCounter : IEquatable<TimestampedCounter> + public readonly record struct TimestampedCounter(uint Count, uint UnixMs) { /// <summary> /// The time the count was last modifed /// </summary> - public readonly DateTimeOffset LastModified; - /// <summary> - /// The last failed login attempt count value - /// </summary> - public readonly uint Count; - - /// <summary> - /// Initalizes a new flc structure with the current UTC date - /// and the specified count value - /// </summary> - /// <param name="count">FLC current count</param> - public TimestampedCounter(uint count) : this(DateTimeOffset.UtcNow, count) - { } - - private TimestampedCounter(DateTimeOffset dto, uint count) - { - Count = count; - LastModified = dto; - } - - /// <summary> - /// Compacts and converts the counter value and timestamp into - /// a 64bit unsigned integer - /// </summary> - /// <param name="count">The counter to convert</param> - public static explicit operator ulong(TimestampedCounter count) => count.ToUInt64(); + public readonly DateTimeOffset LastModified => DateTimeOffset.FromUnixTimeSeconds(UnixMs); /// <summary> /// Compacts and converts the counter value and timestamp into /// a 64bit unsigned integer /// </summary> /// <returns>The uint64 compacted value</returns> - public ulong ToUInt64() + public readonly ulong ToUInt64() { //Upper 32 bits time, lower 32 bits count - ulong value = (ulong)LastModified.ToUnixTimeSeconds() << 32; + ulong value = UnixMs; + //Lshift to upper32 + value <<= 32; + //Set count as lower32 value |= Count; return value; } @@ -82,24 +58,6 @@ namespace VNLib.Plugins.Essentials /// The previously compacted <see cref="TimestampedCounter"/> /// value to cast back to a counter /// </summary> - /// <param name="value"></param> - public static explicit operator TimestampedCounter(ulong value) => FromUInt64(value); - - ///<inheritdoc/> - public override bool Equals(object? obj) => obj is TimestampedCounter counter && Equals(counter); - ///<inheritdoc/> - public override int GetHashCode() => this.ToUInt64().GetHashCode(); - ///<inheritdoc/> - public static bool operator ==(TimestampedCounter left, TimestampedCounter right) => left.Equals(right); - ///<inheritdoc/> - public static bool operator !=(TimestampedCounter left, TimestampedCounter right) => !(left == right); - ///<inheritdoc/> - public bool Equals(TimestampedCounter other) => ToUInt64() == other.ToUInt64(); - - /// <summary> - /// The previously compacted <see cref="TimestampedCounter"/> - /// value to cast back to a counter - /// </summary> /// <param name="value">The uint64 encoded <see cref="TimestampedCounter"/></param> /// <returns> /// The decoded <see cref="TimestampedCounter"/> from its @@ -108,10 +66,39 @@ namespace VNLib.Plugins.Essentials public static TimestampedCounter FromUInt64(ulong value) { //Upper 32 bits time, lower 32 bits count - long time = (long)(value >> 32); + uint time = (uint)(value >> 32); uint count = (uint)(value & uint.MaxValue); - //Init dto struct - return new(DateTimeOffset.FromUnixTimeSeconds(time), count); + + return new TimestampedCounter(count, time); } + + /// <summary> + /// Creates a new <see cref="TimestampedCounter"/> from the given counter + /// value and the <see cref="DateTimeOffset"/> unix ms value + /// </summary> + /// <param name="count"></param> + /// <param name="time">The internal time to store in the counter</param> + /// <returns>An initialized <see cref="TimestampedCounter"/></returns> + /// <exception cref="StackOverflowException"></exception> + public static TimestampedCounter FromValues(uint count, DateTimeOffset time) + { + //The time in seconds truncated to a uint32 + uint sec = Convert.ToUInt32(time.ToUnixTimeSeconds()); + return new TimestampedCounter(count, sec); + } + + /// <summary> + /// The previously compacted <see cref="TimestampedCounter"/> + /// value to cast back to a counter + /// </summary> + /// <param name="value"></param> + public static explicit operator TimestampedCounter(ulong value) => FromUInt64(value); + + /// <summary> + /// Compacts and converts the counter value and timestamp into + /// a 64bit unsigned integer + /// </summary> + /// <param name="count">The counter to convert</param> + public static explicit operator ulong(TimestampedCounter count) => count.ToUInt64(); } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 7a6b4ef..7a7cb6a 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -71,7 +71,6 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - this.ThrowIfClosed(); int len = Convert.ToInt32(_length); return _length == 0 ? Span<T>.Empty : new Span<T>(Base, len); } @@ -116,6 +115,22 @@ namespace VNLib.Utils.Memory handle = initial; } + /* + * Empty handle will disable release, and because the + * handle pointer is 0, its considered invalid and will now + * allow + */ + /// <summary> + /// Initialzies an empty memory handle. Properties will raise exceptions + /// when accessed, however <see cref="IMemoryHandle{T}"/> operations are + /// considered "safe" meaning they should never raise excpetions + /// </summary> + public MemoryHandle():base(false) + { + _length = 0; + Heap = null!; + } + /// <summary> /// Resizes the current handle on the heap /// </summary> @@ -131,6 +146,12 @@ namespace VNLib.Utils.Memory //Re-alloc (Zero if required) try { + /* + * If resize raises an exception the current block pointer + * should still be valid, if its not, the pointer should + * be set to 0/-1, which will be considered invalid anyway + */ + Heap.Resize(ref handle, Length, (nuint)sizeof(T), ZeroMemory); } //Catch the disposed exception so we can invalidate the current ptr @@ -143,6 +164,7 @@ namespace VNLib.Utils.Memory throw; } } + /// <summary> /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks /// </summary> @@ -195,7 +217,7 @@ namespace VNLib.Utils.Memory ///<inheritdoc/> protected override bool ReleaseHandle() { - //Return result of free + //Return result of free, only if the handle is valid return Heap.Free(ref handle); } @@ -206,14 +228,14 @@ namespace VNLib.Utils.Memory /// <returns>true if the block of memory is the same, false if the handle's size does not /// match or the base addresses do not match even if they point to an overlapping address space</returns> /// <exception cref="ObjectDisposedException"></exception> - public bool Equals(MemoryHandle<T> other) + public bool Equals(MemoryHandle<T>? other) { - this.ThrowIfClosed(); - other.ThrowIfClosed(); - return _length == other._length && handle == other.handle; + return other != null && IsClosed == other.IsClosed && _length == other._length && handle == other.handle; } + ///<inheritdoc/> - public override bool Equals(object obj) => obj is MemoryHandle<T> oHandle && Equals(oHandle); + public override bool Equals(object? obj) => obj is MemoryHandle<T> oHandle && Equals(oHandle); + ///<inheritdoc/> public override int GetHashCode() => base.GetHashCode(); diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index d890757..f7ab8d4 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -176,5 +176,43 @@ namespace VNLib.Utils.Memory.Tests handle.ResizeIfSmaller(4096); Assert.IsTrue(handle.Length == 4096); } + + [TestMethod] + public unsafe void EmptyHandleTest() + { + //Confirm that an empty handle does not raise exceptions when in IMemoryHandle + using (IMemoryHandle<byte> thandle = new MemoryHandle<byte>()) + { + Assert.IsTrue(thandle.Length == 0); + + Assert.IsTrue(thandle.Span == Span<byte>.Empty); + + //Empty span should not throw + _ = thandle.AsSpan(0); + + //Pin should throw + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = thandle.Pin(0)); + } + + //Full ref to mhandle check status + using (MemoryHandle<byte> mHandle = new()) + { + + //Some members should not throw + _ = mHandle.ByteLength; + + //Handle should be invalid + Assert.IsTrue(mHandle.IsInvalid); + + Assert.IsFalse(mHandle.IsClosed); + + //Confirm empty handle protected values throw + Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = mHandle.GetOffset(0)); + + Assert.ThrowsException<ObjectDisposedException>(() => mHandle.Resize(10)); + + Assert.ThrowsException<ArgumentOutOfRangeException>(() => mHandle.BasePtr); + } + } } } |