diff options
Diffstat (limited to 'libs')
15 files changed, 191 insertions, 236 deletions
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; - + /// <summary> /// Initiailzes a new <see cref="GlobalCacheStore"/> with the backing <see cref="IGlobalCacheProvider"/> /// global cache /// </summary> /// <param name="globalCache">The backing cache store</param> /// <param name="bufferSize">The size of the buffer used to serialize session objects</param> + /// <param name="debugLog">An optional log provider for writing serializing events to</param> /// <exception cref="ArgumentNullException"></exception> - 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); } ///<inheritdoc/> @@ -64,7 +66,7 @@ namespace VNLib.Plugins.Sessions.Cache.Client } ///<inheritdoc/> - public Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default) + public Task<bool> 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 /// <returns>A task that resolves the found object or null otherwise</returns> Task<T?> GetObjectAsync<T>(string objectId, CancellationToken cancellationToken = default); + /// <summary> + /// Adds or updates an object in the cache provider by session id and optionally it's new id + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="objectId">The session id to add or update</param> + /// <param name="newId">The uniqe id of the item to update</param> + /// <param name="obj"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> Task AddOrUpdateObjectAsync<T>(string objectId, string? newId, T obj, CancellationToken cancellationToken = default); - Task DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default); + /// <summary> + /// Deletes an object from the cache provider by session id + /// </summary> + /// <param name="objectId">The id of the item to delete</param> + /// <param name="cancellationToken">A token to cancel the operation</param> + /// <returns>A value that indicates if the item was found and deleted</returns> + Task<bool> DeleteObjectAsync(string objectId, CancellationToken cancellationToken = default); /// <summary> /// 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 /// </summary> - 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<T>(ReadOnlySpan<byte> objectData) where T : default + [Conditional("DEBUG")] + private void DebugSessionItems(IDictionary<string, string> sessionData, bool serializing) + { + if (_debugLog is null) + { + return; + } + + StringBuilder sdBuilder = new(); + foreach (KeyValuePair<string, string> 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<T>(ReadOnlySpan<byte> objectData) where T : default { if (!typeof(T).IsAssignableTo(typeof(IDictionary<string, string>))) { @@ -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<char> reader) => reader.Window.IndexOf(KV_DELIMITER); - void ICacheObjectSerialzer.Serialize<T>(T obj, IBufferWriter<byte> finiteWriter) + void ICacheObjectSerializer.Serialize<T>(T obj, IBufferWriter<byte> finiteWriter) { if(obj is not Dictionary<string, string> 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<char> charBuffer = MemoryUtil.UnsafeAllocNearestPage<char>(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 /// <summary> /// The backing cache store /// </summary> - protected abstract IRemoteCacheStore Cache { get; } + protected abstract IRemoteCacheStore Cache { get; } /// <summary> /// 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 /// </summary> - [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<OAuth2SessionProvider>(); + TokenFactory = tokenFactory; Applications = new(pbase.GetContextOptions(), pbase.GetOrCreateSingleton<ManagedPasswordHashing>()); 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<ISessionProvider>(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<OAuth2SessionProvider>(); + 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<SessionHandle> 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<AccessTokenEndpoint>(); - } - - //Optional revocation endpoint - if (plugin.HasConfigForType<RevocationEndpoint>()) - { - //Route revocation endpoint - plugin.Route<RevocationEndpoint>(); - } - - //Init session provider - _sessions = plugin.GetOrCreateSingleton<OAuth2SessionProvider>(); - _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 /// <summary> /// Provides OAuth2 session management /// </summary> - [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<OAuth2SessionStore>(); @@ -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<RevocationEndpoint>()) + { + //Route revocation endpoint + plugin.Route<RevocationEndpoint>(); + } } - 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<SessionHandle> 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<OAuth2Session> { const int MAX_SESSION_BUFFER_SIZE = 16 * 1024; private ILogProvider _log; + ///<inheritdoc/> protected override ISessionIdFactory IdFactory { get; } + + ///<inheritdoc/> protected override IRemoteCacheStore Cache { get; } + + ///<inheritdoc/> protected override ISessionFactory<OAuth2Session> SessionFactory { get; } + + ///<inheritdoc/> protected override ILogProvider Log => _log; public bool IsConnected => Cache.IsConnected; @@ -56,11 +62,16 @@ namespace VNLib.Plugins.Sessions.OAuth OAuth2SessionConfig o2Conf = config.DeserialzeAndValidate<OAuth2SessionConfig>(); //Get global cache - IGlobalCacheProvider cache = plugin.GetOrCreateSingleton<VnGlobalCache>() + 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<OAuth2TokenFactory>(); @@ -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 /// <see cref="ISessionIdFactory"/> implementation, using /// http cookies as session id storage /// </summary> - [ConfigurationName(WebSessionProviderEntry.WEB_SESSION_CONFIG)] + [ConfigurationName(WebSessionProvider.WEB_SESSION_CONFIG)] internal sealed class WebSessionIdFactory : ISessionIdFactory { public TimeSpan ValidFor { get; } ///<inheritdoc/> public bool RegenerationSupported { get; } = true; + ///<inheritdoc/> 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<WebSessionStore>(); - } - 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; + + ///<inheritdoc/> public ValueTask<SessionHandle> 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<SessionHandle> AwaitAsyncGet(ValueTask<WebSession?> 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<SessionHandle> ISessionProvider.GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) - { - return _sessions!.GetSessionAsync(entity, cancellationToken); - } - - - public void Load(PluginBase plugin, ILogProvider localized) - { - //Load session provider - _sessions = plugin.GetOrCreateSingleton<WebSessionProvider>(); - - 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<WebSession> { const int MAX_SESSION_BUFFER_SIZE = 16 * 1024; @@ -49,10 +49,10 @@ namespace VNLib.Plugins.Sessions.VNCache protected override ISessionFactory<WebSession> 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<WebSessionIdFactory>(); + IdFactory = plugin.GetOrCreateSingleton<WebSessionIdFactory>(); //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<VnGlobalCache>() + 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; - } /// <summary> /// A value that indicates if the remote cache client is connected |