From 9ddece0eac4dc3718c4f9279b4695d645a3e3cef Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 2 Nov 2023 01:50:06 -0400 Subject: also carried away --- .../src/GlobalCacheStore.cs | 10 +-- .../src/IRemoteCacheStore.cs | 17 ++++- .../src/SessionDataSerialzer.cs | 47 +++++++++++-- .../src/SessionStore.cs | 9 +-- .../src/Endpoints/AccessTokenEndpoint.cs | 6 +- .../src/Endpoints/RevocationEndpoint.cs | 3 +- .../src/O2AuthenticationPluginEntry.cs | 29 ++++---- .../src/O2SessionProviderEntry.cs | 81 ---------------------- .../src/OAuth2SessionProvider.cs | 54 +++++++++++---- .../src/OAuth2SessionStore.cs | 27 +++++--- .../src/OAuth2TokenFactory.cs | 2 +- .../src/WebSessionIdFactory.cs | 26 ++++--- .../src/WebSessionProvider.cs | 31 ++++++--- .../src/WebSessionProviderEntry.cs | 61 ---------------- .../src/WebSessionStore.cs | 24 +++---- .../SessionProvider/src/RuntimeSessionProvider.cs | 54 ++++----------- .../SessionProvider/src/SessionProviderEntry.cs | 76 +++++++------------- 17 files changed, 229 insertions(+), 328 deletions(-) delete mode 100644 libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs delete mode 100644 libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProviderEntry.cs diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs index 89b2b5b..1770f0a 100644 --- a/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs +++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/GlobalCacheStore.cs @@ -27,6 +27,7 @@ using System.Threading; using System.Threading.Tasks; using VNLib.Data.Caching; +using VNLib.Utils.Logging; namespace VNLib.Plugins.Sessions.Cache.Client { @@ -40,18 +41,19 @@ namespace VNLib.Plugins.Sessions.Cache.Client private readonly IGlobalCacheProvider _cache; private readonly SessionDataSerialzer _serialzer; - + /// /// Initiailzes a new with the backing /// global cache /// /// The backing cache store /// The size of the buffer used to serialize session objects + /// An optional log provider for writing serializing events to /// - public GlobalCacheStore(IGlobalCacheProvider globalCache, int bufferSize) + public GlobalCacheStore(IGlobalCacheProvider globalCache, int bufferSize, ILogProvider? debugLog) { _cache = globalCache ?? throw new ArgumentNullException(nameof(globalCache)); - _serialzer = new(bufferSize); + _serialzer = new(bufferSize, debugLog); } /// @@ -64,7 +66,7 @@ namespace VNLib.Plugins.Sessions.Cache.Client } /// - public Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default) + public Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default) { return _cache.DeleteAsync(objectId, cancellationToken); } diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs index aa2e3a7..b876c6c 100644 --- a/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs +++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/IRemoteCacheStore.cs @@ -41,9 +41,24 @@ namespace VNLib.Plugins.Sessions.Cache.Client /// A task that resolves the found object or null otherwise Task GetObjectAsync(string objectId, CancellationToken cancellationToken = default); + /// + /// Adds or updates an object in the cache provider by session id and optionally it's new id + /// + /// + /// The session id to add or update + /// The uniqe id of the item to update + /// + /// + /// Task AddOrUpdateObjectAsync(string objectId, string? newId, T obj, CancellationToken cancellationToken = default); - Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default); + /// + /// Deletes an object from the cache provider by session id + /// + /// The id of the item to delete + /// A token to cancel the operation + /// A value that indicates if the item was found and deleted + Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default); /// /// Gets a value that determines if the remote cache store is available diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionDataSerialzer.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionDataSerialzer.cs index 1cebdf6..94bb1c3 100644 --- a/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionDataSerialzer.cs +++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionDataSerialzer.cs @@ -25,9 +25,11 @@ using System; using System.Text; using System.Buffers; +using System.Diagnostics; using System.Collections.Generic; using VNLib.Utils.Memory; +using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Data.Caching; @@ -38,18 +40,48 @@ namespace VNLib.Plugins.Sessions.Cache.Client /// Very basic session data serializer memory optimized for key-value /// string pairs /// - internal sealed class SessionDataSerialzer : ICacheObjectSerialzer, ICacheObjectDeserialzer + internal sealed class SessionDataSerialzer : ICacheObjectSerializer, ICacheObjectDeserializer { const string KV_DELIMITER = "\0\0"; readonly int CharBufferSize; + readonly ILogProvider? _debugLog; - public SessionDataSerialzer(int charBufferSize) + public SessionDataSerialzer(int charBufferSize, ILogProvider? debugLog) { CharBufferSize = charBufferSize; + _debugLog = debugLog; + debugLog?.Warn("Sensitive session logging is enabled. This will leak session data!"); } - T? ICacheObjectDeserialzer.Deserialze(ReadOnlySpan objectData) where T : default + [Conditional("DEBUG")] + private void DebugSessionItems(IDictionary sessionData, bool serializing) + { + if (_debugLog is null) + { + return; + } + + StringBuilder sdBuilder = new(); + foreach (KeyValuePair item in sessionData) + { + sdBuilder.Append(item.Key); + sdBuilder.Append('='); + sdBuilder.Append(item.Value); + sdBuilder.Append(", "); + } + + if (serializing) + { + _debugLog.Debug("Serialzing session data: {sd} ", sdBuilder); + } + else + { + _debugLog.Debug("Deserialzing session data: {sd} ", sdBuilder); + } + } + + T? ICacheObjectDeserializer.Deserialize(ReadOnlySpan objectData) where T : default { if (!typeof(T).IsAssignableTo(typeof(IDictionary))) { @@ -107,18 +139,23 @@ namespace VNLib.Plugins.Sessions.Cache.Client reader.Advance(sep + 2); } + DebugSessionItems(output, false); + return (T?)(output as object); } private static int GetNextToken(ref ForwardOnlyReader reader) => reader.Window.IndexOf(KV_DELIMITER); - void ICacheObjectSerialzer.Serialize(T obj, IBufferWriter finiteWriter) + void ICacheObjectSerializer.Serialize(T obj, IBufferWriter finiteWriter) { if(obj is not Dictionary dict) { throw new NotSupportedException("Data type is not supported by this serializer"); } - + + //Write debug info + DebugSessionItems(dict, true); + //Alloc char buffer, sessions should be under 16k using UnsafeMemoryHandle charBuffer = MemoryUtil.UnsafeAllocNearestPage(CharBufferSize); diff --git a/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionStore.cs b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionStore.cs index 2709a65..0797efb 100644 --- a/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionStore.cs +++ b/libs/VNLib.Plugins.Sessions.Cache.Client/src/SessionStore.cs @@ -29,7 +29,6 @@ using System.Collections.Generic; using VNLib.Net.Http; using VNLib.Utils.Logging; -using VNLib.Data.Caching.Exceptions; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Sessions.Cache.Client.Exceptions; @@ -58,7 +57,7 @@ namespace VNLib.Plugins.Sessions.Cache.Client /// /// The backing cache store /// - protected abstract IRemoteCacheStore Cache { get; } + protected abstract IRemoteCacheStore Cache { get; } /// /// The session factory, produces sessions from their initial data and session-id @@ -307,11 +306,7 @@ namespace VNLib.Plugins.Sessions.Cache.Client try { //Update the session's data async - await Cache.DeleteObjectAsync(session.SessionID); - } - catch(ObjectNotFoundException) - { - //ingore onfe, if the session does not exist in cache + _ = await Cache.DeleteObjectAsync(session.SessionID); } catch (Exception ex) { diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs index 77cad38..dc0530f 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs @@ -46,7 +46,6 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints /// Grants authorization to OAuth2 clients to protected resources /// with access tokens /// - [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)] internal sealed class AccessTokenEndpoint : ResourceEndpointBase { private readonly IApplicationTokenFactory TokenFactory; @@ -60,14 +59,13 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints DisableSessionsRequired = true }; - public AccessTokenEndpoint(PluginBase pbase, IConfigScope config) + public AccessTokenEndpoint(PluginBase pbase, IConfigScope config, IApplicationTokenFactory tokenFactory) { string? path = config["token_path"].GetString();; InitPathAndLog(path, pbase.Log); - //Get the session provider, as its a token factory - TokenFactory = pbase.GetOrCreateSingleton(); + TokenFactory = tokenFactory; Applications = new(pbase.GetContextOptions(), pbase.GetOrCreateSingleton()); diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs index 45a8391..bdb4e4e 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs @@ -46,8 +46,7 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints { //Revoke the access token, by invalidating it entity.Session.Invalidate(); - entity.CloseResponse(System.Net.HttpStatusCode.OK); - return VfReturnType.VirtualSkip; + return VirtualOk(entity); } } } diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/O2AuthenticationPluginEntry.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/O2AuthenticationPluginEntry.cs index 5cd6aaf..1d74b7e 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/O2AuthenticationPluginEntry.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/O2AuthenticationPluginEntry.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -23,9 +23,13 @@ */ using System; -using System.Collections.Generic; + +using System.ComponentModel.Design; using VNLib.Utils.Logging; +using VNLib.Plugins.Attributes; +using VNLib.Plugins.Essentials.Sessions; +using VNLib.Plugins.Extensions.Loading; namespace VNLib.Plugins.Sessions.OAuth { @@ -33,19 +37,20 @@ namespace VNLib.Plugins.Sessions.OAuth { public override string PluginName => "Essentials.Oauth.Authentication"; - private readonly O2SessionProviderEntry SessionProvider = new(); + private OAuth2SessionProvider? SessionProvider; + + + [ServiceConfigurator] + public void OnServicesLoading(IServiceContainer services) + { + //Expose the OAuth2 session provider as a service singleton + services.AddService(SessionProvider!); + } protected override void OnLoad() { - try - { - //Load the session provider, that will only load the endpoints - SessionProvider.Load(this, Log); - } - catch(KeyNotFoundException kne) - { - Log.Error("Missing required configuration keys {err}", kne.Message); - } + SessionProvider = this.GetOrCreateSingleton(); + Log.Information("Plugin loaded"); } protected override void OnUnLoad() diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs deleted file mode 100644 index 0437541..0000000 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/O2SessionProviderEntry.cs +++ /dev/null @@ -1,81 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Sessions.OAuth -* File: O2SessionProviderEntry.cs -* -* O2SessionProviderEntry.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Sessions.OAuth is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Sessions.OAuth is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Net.Http; -using VNLib.Utils.Logging; -using VNLib.Plugins.Sessions.OAuth.Endpoints; -using VNLib.Plugins.Essentials.Sessions; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Extensions.Loading.Routing; - -namespace VNLib.Plugins.Sessions.OAuth -{ - - public sealed class O2SessionProviderEntry : ISessionProvider - { - public const string OAUTH2_CONFIG_KEY = "oauth2"; - - private OAuth2SessionProvider? _sessions; - - public bool CanProcess(IHttpEvent entity) - { - //If authorization header is set try to process as oauth2 session - return _sessions != null && _sessions.IsConnected && entity.Server.Headers.HeaderSet(System.Net.HttpRequestHeader.Authorization); - } - - ValueTask ISessionProvider.GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) - { - return _sessions!.GetSessionAsync(entity, cancellationToken); - } - - public void Load(PluginBase plugin, ILogProvider localized) - { - IConfigScope o2Config = plugin.GetConfig(OAUTH2_CONFIG_KEY); - - //Access token endpoint is optional - if (o2Config.ContainsKey("token_path")) - { - //Create token endpoint - plugin.Route(); - } - - //Optional revocation endpoint - if (plugin.HasConfigForType()) - { - //Route revocation endpoint - plugin.Route(); - } - - //Init session provider - _sessions = plugin.GetOrCreateSingleton(); - _sessions.SetLog(localized); - - localized.Information("Session provider loaded"); - } - } -} \ No newline at end of file diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs index b1b97b7..99bac29 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs @@ -5,8 +5,8 @@ * Package: VNLib.Plugins.Essentials.Sessions.OAuth * File: OAuth2SessionProvider.cs * -* OAuth2SessionProvider.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth which is part of the larger -* VNLib collection of libraries and utilities. +* OAuth2SessionProvider.cs is part of VNLib.Plugins.Essentials.Sessions.OAuth +* which is part of the larger VNLib collection of libraries and utilities. * * VNLib.Plugins.Essentials.Sessions.OAuth is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -31,7 +31,6 @@ using System.Collections.Generic; using VNLib.Net.Http; using VNLib.Utils; using VNLib.Utils.Logging; -using VNLib.Data.Caching.Exceptions; using VNLib.Plugins.Essentials; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Essentials.Oauth.Tokens; @@ -39,6 +38,8 @@ using VNLib.Plugins.Essentials.Oauth.Applications; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.Loading.Sql; using VNLib.Plugins.Extensions.Loading.Events; +using VNLib.Plugins.Extensions.Loading.Routing; +using VNLib.Plugins.Sessions.OAuth.Endpoints; using static VNLib.Plugins.Essentials.Oauth.OauthSessionExtensions; namespace VNLib.Plugins.Sessions.OAuth @@ -47,9 +48,12 @@ namespace VNLib.Plugins.Sessions.OAuth /// /// Provides OAuth2 session management /// - [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)] - internal sealed class OAuth2SessionProvider : ISessionProvider, ITokenManager, IApplicationTokenFactory, IIntervalScheduleable - { + [ExternService] + [ConfigurationName(OAUTH2_CONFIG_KEY)] + public sealed class OAuth2SessionProvider : ISessionProvider, ITokenManager, IApplicationTokenFactory, IIntervalScheduleable + { + public const string OAUTH2_CONFIG_KEY = "oauth2"; + private static readonly SessionHandle Skip = new(null, FileProcessArgs.VirtualSkip, null); private readonly OAuth2SessionStore _sessions; @@ -60,8 +64,6 @@ namespace VNLib.Plugins.Sessions.OAuth private uint _waitingConnections; - public bool IsConnected => _sessions.IsConnected; - public OAuth2SessionProvider(PluginBase plugin, IConfigScope config) { _sessions = plugin.GetOrCreateSingleton(); @@ -71,9 +73,40 @@ namespace VNLib.Plugins.Sessions.OAuth //Schedule interval plugin.ScheduleInterval(this, TimeSpan.FromMinutes(2)); + + IConfigScope o2Config = plugin.GetConfig(OAUTH2_CONFIG_KEY); + + /* + * Route built-in oauth2 endpoints + */ + if (o2Config.ContainsKey("token_path")) + { + /* + * Access token endpoint requires this instance as a token manager + * which would cause a circular dependency, so it needs to be routed + * manually + */ + AccessTokenEndpoint tokenEndpoint = new(plugin, o2Config, this); + //Create token endpoint + plugin.Route(tokenEndpoint); + } + + //Optional revocation endpoint + if (plugin.HasConfigForType()) + { + //Route revocation endpoint + plugin.Route(); + } } - public void SetLog(ILogProvider log) => _sessions.SetLog(log); + /* + * Called when + */ + public bool CanProcess(IHttpEvent entity) + { + //If authorization header is set try to process as oauth2 session + return _sessions.IsConnected && entity.Server.Headers.HeaderSet(HttpRequestHeader.Authorization); + } public ValueTask GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) { @@ -217,9 +250,6 @@ namespace VNLib.Plugins.Sessions.OAuth //Remove tokens by thier object id from cache await _sessions.DeleteTokenAsync(token.Id, cancellationToken); } - //Ignore if the object has already been removed - catch (ObjectNotFoundException) - {} catch (Exception ex) { #pragma warning disable CA1508 // Avoid dead conditional code diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs index 8c65bc8..6cf25c4 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionStore.cs @@ -34,19 +34,25 @@ using VNLib.Plugins.Sessions.Cache.Client; using VNLib.Plugins.Extensions.VNCache; using VNLib.Plugins.Extensions.Loading; - namespace VNLib.Plugins.Sessions.OAuth { - [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)] + [ConfigurationName(OAuth2SessionProvider.OAUTH2_CONFIG_KEY)] internal sealed class OAuth2SessionStore : SessionStore { const int MAX_SESSION_BUFFER_SIZE = 16 * 1024; private ILogProvider _log; + /// protected override ISessionIdFactory IdFactory { get; } + + /// protected override IRemoteCacheStore Cache { get; } + + /// protected override ISessionFactory SessionFactory { get; } + + /// protected override ILogProvider Log => _log; public bool IsConnected => Cache.IsConnected; @@ -56,11 +62,16 @@ namespace VNLib.Plugins.Sessions.OAuth OAuth2SessionConfig o2Conf = config.DeserialzeAndValidate(); //Get global cache - IGlobalCacheProvider cache = plugin.GetOrCreateSingleton() + IGlobalCacheProvider? cache = plugin.GetDefaultGlobalCache()? .GetPrefixedCache(o2Conf.CachePrefix, HashAlg.SHA256); - //Create remote cache - Cache = new GlobalCacheStore(cache, MAX_SESSION_BUFFER_SIZE); + _ = cache ?? throw new MissingDependencyException("A global cache provider is required to store OAuth2 sessions. Please configure a cache provider"); + + //Get debug log if enabled + ILogProvider? sessDebugLog = plugin.HostArgs.HasArgument("--debug-sessions") ? plugin.Log.CreateScope("OAuth2-Sessions") : null; + + //Create cache store from global cache + Cache = new GlobalCacheStore(cache, MAX_SESSION_BUFFER_SIZE, sessDebugLog); IdFactory = plugin.GetOrCreateSingleton(); @@ -70,12 +81,6 @@ namespace VNLib.Plugins.Sessions.OAuth _log = plugin.Log; } - public void SetLog(ILogProvider log) - { - _log = log; - } - - public Task DeleteTokenAsync(string token, CancellationToken cancellation) { return Cache.DeleteObjectAsync(token, cancellation); diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs index b452e29..b97abae 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs @@ -33,7 +33,7 @@ using VNLib.Plugins.Essentials.Extensions; namespace VNLib.Plugins.Sessions.OAuth { - [ConfigurationName(O2SessionProviderEntry.OAUTH2_CONFIG_KEY)] + [ConfigurationName(OAuth2SessionProvider.OAUTH2_CONFIG_KEY)] internal sealed class OAuth2TokenFactory : ISessionIdFactory, IOauthSessionIdFactory { private readonly OAuth2SessionConfig _config; diff --git a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionIdFactory.cs b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionIdFactory.cs index 03d8980..3cdf56b 100644 --- a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionIdFactory.cs +++ b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionIdFactory.cs @@ -38,13 +38,14 @@ namespace VNLib.Plugins.Sessions.VNCache /// implementation, using /// http cookies as session id storage /// - [ConfigurationName(WebSessionProviderEntry.WEB_SESSION_CONFIG)] + [ConfigurationName(WebSessionProvider.WEB_SESSION_CONFIG)] internal sealed class WebSessionIdFactory : ISessionIdFactory { public TimeSpan ValidFor { get; } /// public bool RegenerationSupported { get; } = true; + /// public bool RegenIdOnEmptyEntry { get; } = true; @@ -65,13 +66,14 @@ namespace VNLib.Plugins.Sessions.VNCache _cookieSize = (int)cookieSize; } - public WebSessionIdFactory(PluginBase pbase, IConfigScope config) - { - _cookieSize = (int)config["cookie_size"].GetUInt32(); - SessionCookieName = config["cookie_name"].GetString() - ?? throw new KeyNotFoundException($"Missing required element 'cookie_name' for config '{WebSessionProviderEntry.WEB_SESSION_CONFIG}'"); - ValidFor = config["valid_for_sec"].GetTimeSpan(TimeParseType.Seconds); - } + //Create instance from config + public WebSessionIdFactory(PluginBase plugin, IConfigScope config): + this( + config["cookie_size"].GetUInt32(), + config.GetRequiredProperty("cookie_name", p => p.GetString()!), + config["valid_for_sec"].GetTimeSpan(TimeParseType.Seconds) + ) + { } public string RegenerateId(IHttpEvent entity) @@ -91,17 +93,13 @@ namespace VNLib.Plugins.Sessions.VNCache }; //Set the session id cookie - entity.Server.SetCookie(cookie); + entity.Server.SetCookie(in cookie); //return session-id value from cookie value return sessionId; } - public string? TryGetSessionId(IHttpEvent entity) - { - //Get session cookie - return entity.Server.RequestCookies.GetValueOrDefault(SessionCookieName); - } + public string? TryGetSessionId(IHttpEvent entity) => entity.Server.RequestCookies.GetValueOrDefault(SessionCookieName); public bool CanService(IHttpEvent entity) { diff --git a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProvider.cs b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProvider.cs index 655bae8..5516fef 100644 --- a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProvider.cs +++ b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProvider.cs @@ -27,6 +27,7 @@ using System.Threading; using System.Threading.Tasks; using VNLib.Net.Http; +using VNLib.Utils.Logging; using VNLib.Plugins.Essentials; using VNLib.Plugins.Essentials.Sessions; using VNLib.Plugins.Extensions.Loading; @@ -34,17 +35,20 @@ using VNLib.Plugins.Extensions.Loading; namespace VNLib.Plugins.Sessions.VNCache { - [ConfigurationName(WebSessionProviderEntry.WEB_SESSION_CONFIG)] - internal sealed class WebSessionProvider : ISessionProvider + [ExternService] + [ConfigurationName(WEB_SESSION_CONFIG)] + public sealed class WebSessionProvider : ISessionProvider { + internal const string WEB_SESSION_CONFIG = "web"; + internal const string LOGGER_SCOPE = "WEB-SESSIONS"; + private static readonly SessionHandle _vf = new (null, FileProcessArgs.VirtualSkip, null); private readonly WebSessionStore _sessions; private readonly uint _maxConnections; private uint _waitingConnections; - - public bool IsConnected => _sessions.IsConnected; + public WebSessionProvider(PluginBase plugin, IConfigScope config) { @@ -52,15 +56,20 @@ namespace VNLib.Plugins.Sessions.VNCache //Init session provider _sessions = plugin.GetOrCreateSingleton(); - } - private SessionHandle PostProcess(WebSession? session) - { - return session == null ? SessionHandle.Empty : new SessionHandle(session, OnSessionReleases); + ILogProvider logger = plugin.Log.CreateScope("WEB-SESSIONS"); + + logger.Information("Session provider loaded"); } - private ValueTask OnSessionReleases(ISession session, IHttpEvent entity) => _sessions.ReleaseSessionAsync((WebSession)session, entity); + /* + * This is a dynamic method captured by the session loader to determine if the + * current session provider can process the incoming request. + */ + public bool CanProcess(IHttpEvent entity) => _sessions.IsConnected; + + /// public ValueTask GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) { //Limit max number of waiting clients and make sure were connected @@ -88,6 +97,10 @@ namespace VNLib.Plugins.Sessions.VNCache } } + private SessionHandle PostProcess(WebSession? session) => session == null ? SessionHandle.Empty : new (session, OnSessionReleases); + + private ValueTask OnSessionReleases(ISession session, IHttpEvent entity) => _sessions.ReleaseSessionAsync((WebSession)session, entity); + private async Task AwaitAsyncGet(ValueTask async) { //Inct wait count while async waiting diff --git a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProviderEntry.cs b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProviderEntry.cs deleted file mode 100644 index 3b1b1ac..0000000 --- a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionProviderEntry.cs +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Plugins.Essentials.Sessions.VNCache -* File: WebSessionProviderEntry.cs -* -* WebSessionProviderEntry.cs is part of VNLib.Plugins.Essentials.Sessions.VNCache which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Plugins.Essentials.Sessions.VNCache is free software: you can redistribute it and/or modify -* it under the terms of the GNU Affero General Public License as -* published by the Free Software Foundation, either version 3 of the -* License, or (at your option) any later version. -* -* VNLib.Plugins.Essentials.Sessions.VNCache is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU Affero General Public License for more details. -* -* You should have received a copy of the GNU Affero General Public License -* along with this program. If not, see https://www.gnu.org/licenses/. -*/ - -using System; -using System.Threading; -using System.Threading.Tasks; - -using VNLib.Net.Http; -using VNLib.Utils.Logging; -using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Essentials.Sessions; - -namespace VNLib.Plugins.Sessions.VNCache -{ - - public sealed class WebSessionProviderEntry : ISessionProvider - { - internal const string WEB_SESSION_CONFIG = "web"; - - private WebSessionProvider? _sessions; - - - //Web sessions can always be provided so long as cache is loaded - public bool CanProcess(IHttpEvent entity) => _sessions != null && _sessions.IsConnected; - - ValueTask ISessionProvider.GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) - { - return _sessions!.GetSessionAsync(entity, cancellationToken); - } - - - public void Load(PluginBase plugin, ILogProvider localized) - { - //Load session provider - _sessions = plugin.GetOrCreateSingleton(); - - localized.Information("Session provider loaded"); - } - } -} \ No newline at end of file diff --git a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionStore.cs b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionStore.cs index c0b1e5d..ec515f6 100644 --- a/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionStore.cs +++ b/libs/VNLib.Plugins.Sessions.VNCache/src/WebSessionStore.cs @@ -37,7 +37,7 @@ using VNLib.Plugins.Essentials.Sessions; namespace VNLib.Plugins.Sessions.VNCache { - [ConfigurationName(WebSessionProviderEntry.WEB_SESSION_CONFIG)] + [ConfigurationName(WebSessionProvider.WEB_SESSION_CONFIG)] internal sealed class WebSessionStore : SessionStore { const int MAX_SESSION_BUFFER_SIZE = 16 * 1024; @@ -49,10 +49,10 @@ namespace VNLib.Plugins.Sessions.VNCache protected override ISessionFactory SessionFactory { get; } protected override ILogProvider Log => baseLog!; - public WebSessionStore(PluginBase pbase, IConfigScope config) + public WebSessionStore(PluginBase plugin, IConfigScope config) { //Get id factory - IdFactory = pbase.GetOrCreateSingleton(); + IdFactory = plugin.GetOrCreateSingleton(); //Session factory SessionFactory = new WebSessionFactory(); @@ -62,24 +62,24 @@ namespace VNLib.Plugins.Sessions.VNCache * the config */ - string cachePrefix = config["cache_prefix"].GetString() - ?? throw new KeyNotFoundException($"Missing required element 'cache_prefix' for config '{WebSessionProviderEntry.WEB_SESSION_CONFIG}'"); + string cachePrefix = config.GetRequiredProperty("cache_prefix", p => p.GetString()!); //Create a simple prefix cache provider - IGlobalCacheProvider cache = pbase.GetOrCreateSingleton() + IGlobalCacheProvider? cache = plugin.GetDefaultGlobalCache()? .GetPrefixedCache(cachePrefix, HashAlg.SHA256); + _ = cache ?? throw new MissingDependencyException("A global cache provider is required to store VNCache sessions. Please configure a cache provider"); + + //Get debug log if enabled + ILogProvider? sessDebugLog = plugin.HostArgs.HasArgument("--debug-sessions") ? plugin.Log.CreateScope("VNCache-Sessions") : null; + //Create cache store from global cache - Cache = new GlobalCacheStore(cache, MAX_SESSION_BUFFER_SIZE); + Cache = new GlobalCacheStore(cache, MAX_SESSION_BUFFER_SIZE, sessDebugLog); //Default log to plugin log - baseLog = pbase.Log; + baseLog = plugin.Log.CreateScope(WebSessionProvider.LOGGER_SCOPE); } - public void InitLog(ILogProvider log) - { - baseLog = log; - } /// /// A value that indicates if the remote cache client is connected diff --git a/plugins/SessionProvider/src/RuntimeSessionProvider.cs b/plugins/SessionProvider/src/RuntimeSessionProvider.cs index 28397c9..1058e2e 100644 --- a/plugins/SessionProvider/src/RuntimeSessionProvider.cs +++ b/plugins/SessionProvider/src/RuntimeSessionProvider.cs @@ -26,58 +26,28 @@ using System; using System.Threading; using System.Threading.Tasks; -using VNLib.Utils; -using VNLib.Utils.Logging; using VNLib.Net.Http; -using VNLib.Plugins.Extensions.Loading; +using VNLib.Utils.Resources; namespace VNLib.Plugins.Essentials.Sessions { - internal sealed class RuntimeSessionProvider : VnDisposeable, ISessionProvider + internal sealed class RuntimeSessionProvider : ISessionProvider { - private readonly AssemblyLoader _asm; - - private Func _canProcessMethod; - private Action _loadMethod; - private ISessionProvider _ref; + private readonly Func _canProcessMethod; + private readonly ISessionProvider _ref; - public RuntimeSessionProvider(AssemblyLoader asm) + public RuntimeSessionProvider(ISessionProvider externProvider) { - _asm = asm; + _ref = externProvider; - //Store ref to the resource to avoid loads - _ref = asm.Resource; - - //Get load method - _loadMethod = asm.TryGetMethod>("Load") - ?? throw new MissingMethodException("Provider is missing required Load method"); - - //Load canprocess method - _canProcessMethod = asm.TryGetMethod>("CanProcess") - ?? throw new MissingMethodException("Provider is missing required CanProcess method"); - } - - public ValueTask GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) - { - return _ref.GetSessionAsync(entity, cancellationToken); - } - - public bool CanProcess(IHttpEvent ev) - { - return _canProcessMethod(ev); + //Load canprocess method dynamically + _canProcessMethod = ManagedLibrary.GetMethod>(externProvider, "CanProcess"); } - public void Load(PluginBase pbase, ILogProvider localized) - { - _loadMethod(pbase, localized); - } + /// + public ValueTask GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) => _ref.GetSessionAsync(entity, cancellationToken); - protected override void Free() - { - _asm.Dispose(); - _canProcessMethod = null!; - _loadMethod = null!; - _ref = null!; - } + /// + public bool CanProcess(IHttpEvent ev) => _canProcessMethod(ev); } } diff --git a/plugins/SessionProvider/src/SessionProviderEntry.cs b/plugins/SessionProvider/src/SessionProviderEntry.cs index 61808d1..b5ba6fa 100644 --- a/plugins/SessionProvider/src/SessionProviderEntry.cs +++ b/plugins/SessionProvider/src/SessionProviderEntry.cs @@ -23,7 +23,6 @@ */ using System; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -33,7 +32,6 @@ using System.ComponentModel.Design; using VNLib.Net.Http; using VNLib.Utils; using VNLib.Utils.Logging; -using VNLib.Utils.Extensions; using VNLib.Plugins.Attributes; using VNLib.Plugins.Extensions.Loading; @@ -67,65 +65,46 @@ namespace VNLib.Plugins.Essentials.Sessions { List providers = new(); - try - { - Log.Verbose("Loading all specified session providers"); + Log.Verbose("Loading all specified session providers"); - //Get all provider names - IEnumerable providerAssemblyNames = PluginConfig.GetProperty("provider_assemblies") + //Get all provider names + IEnumerable providerAssemblyNames = PluginConfig.GetProperty("provider_assemblies") .EnumerateArray() .Where(s => s.GetString() != null) .Select(s => s.GetString()!); - - - foreach(string asm in providerAssemblyNames) - { - Log.Verbose("Loading {dll} session provider", asm); - - //Attempt to load provider - AssemblyLoader prov = this.LoadAssembly(asm); - - try - { - //Create localized log - ILogProvider scopded = Log.CreateScope(Path.GetFileName(asm)); - RuntimeSessionProvider p = new(prov); - - //Call load method - p.Load(this, scopded); - - //Add to list - providers.Add(p); - } - catch - { - prov.Dispose(); - throw; - } - } + foreach (string asm in providerAssemblyNames) + { + Log.Verbose("Loading {dll} session provider", asm); - if(providers.Count > 0) + try { - //Create array for searching for providers - _provider = new(providers.ToArray()); + //Attempt to load provider + ISessionProvider prov = this.CreateServiceExternal(asm); - Log.Information("Loaded {count} session providers", providers.Count); + //Add to list + providers.Add(new(prov)); } - else + catch (Exception ex) { - Log.Information("No session providers loaded"); + Log.Error("Failed to load session provider {dll}:\n{error}", asm, ex); } + } - Log.Information("Plugin loaded"); + if (providers.Count > 0) + { + //Create array for searching for providers + _provider = new(providers.ToArray()); + + Log.Information("Loaded {count} session providers", providers.Count); } - catch + else { - //Dispose providers - providers.ForEach(static s => s.Dispose()); - throw; + Log.Information("No session providers loaded"); } + + Log.Information("Plugin loaded"); } protected override void OnUnLoad() @@ -173,16 +152,13 @@ namespace VNLib.Plugins.Essentials.Sessions } //Return empty session - return new ValueTask(SessionHandle.Empty); + return new (SessionHandle.Empty); } protected override void Free() { //Remove current providers so we can dispose them - RuntimeSessionProvider[] current = Interlocked.Exchange(ref ProviderArray, Array.Empty()); - - //Cleanup assemblies - current.TryForeach(static p => p.Dispose()); + ProviderArray = Array.Empty(); } } } -- cgit