From 7088c48dd2014364d6b24891b913ff798132e97a Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 10 Mar 2024 16:14:08 -0400 Subject: Squashed commit of the following: commit 720136fef00095c808f9d5c75449e3fd03e82ca0 Author: vnugent Date: Wed Mar 6 21:33:12 2024 -0500 chore: Took a look around commit 71d6fb8c038adafa4a3a943cb0218cd234ef01ae Author: vnugent Date: Mon Feb 12 20:12:28 2024 -0500 refactor: update to latest sql changes and remove untested oauth feature commit 6941b12b44ccb1c184d9b6e33fbe19c72a0b3428 Author: vnugent Date: Sun Feb 4 01:30:26 2024 -0500 submit pending changes --- .../src/Endpoints/AccessTokenEndpoint.cs | 10 ++--- .../src/Endpoints/RevocationEndpoint.cs | 4 +- .../src/OAuth2Session.cs | 9 ++-- .../src/OAuth2SessionConfig.cs | 5 ++- .../src/OAuth2SessionProvider.cs | 18 ++++---- .../src/OAuth2TokenFactory.cs | 51 ++++++++++++++-------- 6 files changed, 56 insertions(+), 41 deletions(-) diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs index b73a7eb..ef5ff5c 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/AccessTokenEndpoint.cs @@ -58,7 +58,7 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints public AccessTokenEndpoint(PluginBase pbase, IConfigScope config, IApplicationTokenFactory tokenFactory) { - string? path = config["token_path"].GetString();; + string? path = config.GetRequiredProperty("token_path", p => p.GetString()!); InitPathAndLog(path, pbase.Log); @@ -111,14 +111,14 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints } } - entity.CloseResponseError(HttpStatusCode.BadRequest, ErrorType.InvalidClient, "Invalid grant type"); //Default to bad request + entity.CloseResponseError(HttpStatusCode.BadRequest, ErrorType.InvalidClient, "Invalid grant type"); return VfReturnType.VirtualSkip; } private async Task GenerateTokenAsync(HttpEntity entity, UserApplication? app) { - if (app == null) + if (app is null) { //App was not found or the credentials do not match entity.CloseResponseError(HttpStatusCode.UnprocessableEntity, ErrorType.InvalidClient, "The credentials are invalid or do not exist"); @@ -127,9 +127,9 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints IOAuth2TokenResult? result = await TokenFactory.CreateAccessTokenAsync(entity, app, entity.EventCancellation); - if (result == null) + if (result is null) { - entity.CloseResponseError(HttpStatusCode.TooManyRequests, ErrorType.TemporarilyUnabavailable, "You have reached the maximum number of valid tokens for this application"); + entity.CloseResponseError(HttpStatusCode.TooManyRequests, ErrorType.TemporarilyUnavailable, "You have reached the maximum number of valid tokens for this application"); return VfReturnType.VirtualSkip; } diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs index bdb4e4e..d21761c 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/Endpoints/RevocationEndpoint.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -38,7 +38,7 @@ namespace VNLib.Plugins.Sessions.OAuth.Endpoints public RevocationEndpoint(PluginBase pbase, IConfigScope config) { - string? path = config["path"].GetString(); + string? path = config.GetRequiredProperty("path", p => p.GetString()!); InitPathAndLog(path, pbase.Log); } diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs index 6c9f3ab..1281afa 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2Session.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -36,12 +36,9 @@ namespace VNLib.Plugins.Sessions.OAuth /// /// The implementation of the OAuth2 session container for HTTP sessions /// - internal sealed class OAuth2Session : RemoteSession + internal sealed class OAuth2Session(string sessionId, IDictionary data, bool isNew) + : RemoteSession(sessionId, data, isNew) { - public OAuth2Session(string sessionId, IDictionary data, bool isNew) - : base(sessionId, data, isNew) - {} - public void InitNewSession(IHttpEvent entity) { SessionType = SessionType.OAuth2; diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs index 5eb9c0c..9e612d5 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionConfig.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -44,6 +44,9 @@ namespace VNLib.Plugins.Sessions.OAuth [JsonPropertyName("cache_prefix")] public string CachePrefix { get; set; } = "oauth2"; + [JsonPropertyName("access_token_type")] + public string TokenType { get; set; } = "Bearer"; + public void Validate() { if (MaxTokensPerApp < 1) diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs index 20a429b..bc06052 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2SessionProvider.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -74,19 +74,17 @@ 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")) + if (config.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); + AccessTokenEndpoint tokenEndpoint = new(plugin, config, this); //Create token endpoint plugin.Route(tokenEndpoint); } @@ -100,7 +98,8 @@ namespace VNLib.Plugins.Sessions.OAuth } /* - * Called when + * Called in SessionProvider.dll to check if the current request can be processed + * as an oauth2 session */ public bool CanProcess(IHttpEvent entity) { @@ -108,6 +107,7 @@ namespace VNLib.Plugins.Sessions.OAuth return _sessions.IsConnected && entity.Server.Headers.HeaderSet(HttpRequestHeader.Authorization); } + /// public ValueTask GetSessionAsync(IHttpEvent entity, CancellationToken cancellationToken) { //Limit max number of waiting clients and make sure were connected @@ -156,7 +156,7 @@ namespace VNLib.Plugins.Sessions.OAuth private SessionHandle PostProcess(OAuth2Session? session) { - if (session == null) + if (session is null) { return SessionHandle.Empty; } @@ -252,9 +252,7 @@ namespace VNLib.Plugins.Sessions.OAuth } catch (Exception ex) { -#pragma warning disable CA1508 // Avoid dead conditional code - errors ??= new(); -#pragma warning restore CA1508 // Avoid dead conditional code + errors ??= []; errors.Add(ex); } } diff --git a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs index b97abae..6d055df 100644 --- a/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs +++ b/libs/VNLib.Plugins.Sessions.OAuth/src/OAuth2TokenFactory.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials.Sessions.OAuth @@ -23,26 +23,21 @@ */ using System; +using System.Net; +using System.Diagnostics.CodeAnalysis; using VNLib.Hashing; using VNLib.Net.Http; using VNLib.Plugins.Sessions.Cache.Client; using VNLib.Plugins.Extensions.Loading; -using VNLib.Plugins.Essentials.Extensions; - namespace VNLib.Plugins.Sessions.OAuth { [ConfigurationName(OAuth2SessionProvider.OAUTH2_CONFIG_KEY)] - internal sealed class OAuth2TokenFactory : ISessionIdFactory, IOauthSessionIdFactory + internal sealed class OAuth2TokenFactory(PluginBase plugin, IConfigScope config) + : ISessionIdFactory, IOauthSessionIdFactory { - private readonly OAuth2SessionConfig _config; - - public OAuth2TokenFactory(PluginBase plugin, IConfigScope config) - { - //Get the oauth2 config - _config = config.DeserialzeAndValidate(); - } + private readonly OAuth2SessionConfig _config = config.DeserialzeAndValidate(); /* * ID Regeneration is always false as OAuth2 sessions @@ -68,13 +63,10 @@ namespace VNLib.Plugins.Sessions.OAuth TimeSpan IOauthSessionIdFactory.SessionValidFor => TimeSpan.FromSeconds(_config.TokenLifeTimeSeconds); /// - string IOauthSessionIdFactory.TokenType => "Bearer"; + string IOauthSessionIdFactory.TokenType => _config.TokenType; /// - bool ISessionIdFactory.CanService(IHttpEvent entity) - { - return entity.Server.HasAuthorization(out _); - } + bool ISessionIdFactory.CanService(IHttpEvent entity) => HasBearerToken(entity.Server, out _); /// public GetTokenResult GenerateTokensAndId() @@ -93,7 +85,32 @@ namespace VNLib.Plugins.Sessions.OAuth string? ISessionIdFactory.TryGetSessionId(IHttpEvent entity) { - return entity.Server.HasAuthorization(out string? token) ? token : null; + return HasBearerToken(entity.Server, out string ? token) ? token : null; + } + + /// + /// Gets the bearer token from an authorization header + /// + /// + /// The token stored in the user's authorization header + /// True if the authorization header was set, has a Bearer token value + private bool HasBearerToken(IConnectionInfo ci, [NotNullWhen(true)] out string? token) + { + //Get auth header value + string? authorization = ci.Headers[HttpRequestHeader.Authorization]; + + //Check if its set + if (!string.IsNullOrWhiteSpace(authorization)) + { + int bearerIndex = authorization.IndexOf(_config.TokenType, StringComparison.OrdinalIgnoreCase); + + //Calc token offset, get token, and trim any whitespace + token = authorization.AsSpan(bearerIndex + _config.TokenType.Length).Trim().ToString(); + return true; + } + + token = null; + return false; } } } \ No newline at end of file -- cgit