From c512d1b0e7320ab2fc7cfac6fe1eafb90af0b273 Mon Sep 17 00:00:00 2001 From: M Hickford Date: Thu, 19 Oct 2023 21:51:41 +0100 Subject: [PATCH 1/2] use universal Gitea OAuth configuration https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications --- .../Core.Tests/GenericOAuthConfigTests.cs | 50 ++++++++++++++++++- src/shared/Core/GenericHostProvider.cs | 2 +- src/shared/Core/GenericOAuthConfig.cs | 44 ++++++++++++---- 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/src/shared/Core.Tests/GenericOAuthConfigTests.cs b/src/shared/Core.Tests/GenericOAuthConfigTests.cs index 08dfacab4..a0cb5f731 100644 --- a/src/shared/Core.Tests/GenericOAuthConfigTests.cs +++ b/src/shared/Core.Tests/GenericOAuthConfigTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using GitCredentialManager.Tests.Objects; using Xunit; @@ -9,7 +10,9 @@ public class GenericOAuthConfigTests [Fact] public void GenericOAuthConfig_TryGet_Valid_ReturnsTrue() { - var remoteUri = new Uri("https://example.com"); + var protocol = "https"; + var host = "example.com"; + var remoteUri = new Uri($"{protocol}://{host}"); const string expectedClientId = "115845b0-77f8-4c06-a3dc-7d277381fad1"; const string expectedClientSecret = "4D35385D9F24"; const string expectedUserName = "TEST_USER"; @@ -44,7 +47,12 @@ public void GenericOAuthConfig_TryGet_Valid_ReturnsTrue() RemoteUri = remoteUri }; - bool result = GenericOAuthConfig.TryGet(trace, settings, remoteUri, out GenericOAuthConfig config); + var input = new InputArguments(new Dictionary { + {"protocol", protocol}, + {"host", host}, + }); + + bool result = GenericOAuthConfig.TryGet(trace, settings, input, out GenericOAuthConfig config); Assert.True(result); Assert.Equal(expectedClientId, config.ClientId); @@ -57,5 +65,43 @@ public void GenericOAuthConfig_TryGet_Valid_ReturnsTrue() Assert.Equal(expectedUserName, config.DefaultUserName); Assert.True(config.UseAuthHeader); } + + [Fact] + public void GenericOAuthConfig_TryGet_Gitea() + { + var protocol = "https"; + var host = "example.com"; + var remoteUri = new Uri($"{protocol}://{host}"); + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications + const string expectedClientId = "e90ee53c-94e2-48ac-9358-a874fb9e0662"; + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#endpoints + const string authzEndpoint = "/login/oauth/authorize"; + const string tokenEndpoint = "/login/oauth/access_token"; + string[] expectedScopes = Array.Empty(); + var expectedRedirectUri = new Uri("http://127.0.0.1"); + var expectedAuthzEndpoint = new Uri(remoteUri, authzEndpoint); + var expectedTokenEndpoint = new Uri(remoteUri, tokenEndpoint); + + var trace = new NullTrace(); + var settings = new TestSettings + { + RemoteUri = remoteUri + }; + + var input = new InputArguments(new Dictionary { + {"protocol", protocol}, + {"host", host}, + {"wwwauth", "Basic realm=\"Gitea\""} + }); + + bool result = GenericOAuthConfig.TryGet(trace, settings, input, out GenericOAuthConfig config); + + Assert.True(result); + Assert.Equal(expectedClientId, config.ClientId); + Assert.Equal(expectedRedirectUri, config.RedirectUri); + Assert.Equal(expectedScopes, config.Scopes); + Assert.Equal(expectedAuthzEndpoint, config.Endpoints.AuthorizationEndpoint); + Assert.Equal(expectedTokenEndpoint, config.Endpoints.TokenEndpoint); + } } } diff --git a/src/shared/Core/GenericHostProvider.cs b/src/shared/Core/GenericHostProvider.cs index cdba0ba8a..447e465d5 100644 --- a/src/shared/Core/GenericHostProvider.cs +++ b/src/shared/Core/GenericHostProvider.cs @@ -63,7 +63,7 @@ public override async Task GenerateCredentialAsync(InputArguments i // Cannot check WIA or OAuth support for non-HTTP based protocols } // Check for an OAuth configuration for this remote - else if (GenericOAuthConfig.TryGet(Context.Trace, Context.Settings, uri, out GenericOAuthConfig oauthConfig)) + else if (GenericOAuthConfig.TryGet(Context.Trace, Context.Settings, input, out GenericOAuthConfig oauthConfig)) { Context.Trace.WriteLine($"Found generic OAuth configuration for '{uri}':"); Context.Trace.WriteLine($"\tAuthzEndpoint = {oauthConfig.Endpoints.AuthorizationEndpoint}"); diff --git a/src/shared/Core/GenericOAuthConfig.cs b/src/shared/Core/GenericOAuthConfig.cs index 0e2a74b75..09d21436f 100644 --- a/src/shared/Core/GenericOAuthConfig.cs +++ b/src/shared/Core/GenericOAuthConfig.cs @@ -1,32 +1,56 @@ using System; +using System.Collections.Generic; +using System.Linq; using GitCredentialManager.Authentication.OAuth; namespace GitCredentialManager { public class GenericOAuthConfig { - public static bool TryGet(ITrace trace, ISettings settings, Uri remoteUri, out GenericOAuthConfig config) + public static bool TryGet(ITrace trace, ISettings settings, InputArguments input, out GenericOAuthConfig config) { config = new GenericOAuthConfig(); + Uri authzEndpointUri = null; + Uri tokenEndpointUri = null; + var remoteUri = input.GetRemoteUri(); - if (!settings.TryGetSetting( + if (input.WwwAuth.Any(x => x.Contains("Basic realm=\"Gitea\""))) + { + trace.WriteLine($"Using universal Gitea OAuth configuration"); + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications + config.ClientId = "e90ee53c-94e2-48ac-9358-a874fb9e0662"; + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#endpoints + authzEndpointUri = new Uri(remoteUri, "/login/oauth/authorize"); + tokenEndpointUri = new Uri(remoteUri, "/login/oauth/access_token"); + config.RedirectUri = new Uri("http://127.0.0.1"); + } + + if (settings.TryGetSetting( Constants.EnvironmentVariables.OAuthAuthzEndpoint, Constants.GitConfiguration.Credential.SectionName, Constants.GitConfiguration.Credential.OAuthAuthzEndpoint, - out string authzEndpoint) || - !Uri.TryCreate(remoteUri, authzEndpoint, out Uri authzEndpointUri)) + out string authzEndpoint)) + { + Uri.TryCreate(remoteUri, authzEndpoint, out authzEndpointUri); + } + + if (authzEndpointUri == null) { trace.WriteLine($"Invalid OAuth configuration - missing/invalid authorize endpoint: {authzEndpoint}"); config = null; return false; } - if (!settings.TryGetSetting( + if (settings.TryGetSetting( Constants.EnvironmentVariables.OAuthTokenEndpoint, Constants.GitConfiguration.Credential.SectionName, Constants.GitConfiguration.Credential.OAuthTokenEndpoint, - out string tokenEndpoint) || - !Uri.TryCreate(remoteUri, tokenEndpoint, out Uri tokenEndpointUri)) + out string tokenEndpoint)) + { + Uri.TryCreate(remoteUri, tokenEndpoint, out tokenEndpointUri); + } + + if (tokenEndpointUri == null) { trace.WriteLine($"Invalid OAuth configuration - missing/invalid token endpoint: {tokenEndpoint}"); config = null; @@ -74,12 +98,12 @@ public static bool TryGet(ITrace trace, ISettings settings, Uri remoteUri, out G Constants.EnvironmentVariables.OAuthRedirectUri, Constants.GitConfiguration.Credential.SectionName, Constants.GitConfiguration.Credential.OAuthRedirectUri, - out string redirectUrl) && - Uri.TryCreate(redirectUrl, UriKind.Absolute, out Uri redirectUri)) + out string redirectUrl) && Uri.TryCreate(redirectUrl, UriKind.Absolute, out Uri redirectUri)) { config.RedirectUri = redirectUri; } - else + + if (config.RedirectUri == null) { trace.WriteLine($"Invalid OAuth configuration - missing/invalid redirect URI: {redirectUrl}"); config = null; From 66292a90f90492713f30e58e62fe5e614fdcadff Mon Sep 17 00:00:00 2001 From: M Hickford Date: Mon, 23 Oct 2023 21:53:37 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Matthew John Cheetham --- .../Core.Tests/GenericOAuthConfigTests.cs | 12 ++++------- src/shared/Core/GenericOAuthConfig.cs | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/shared/Core.Tests/GenericOAuthConfigTests.cs b/src/shared/Core.Tests/GenericOAuthConfigTests.cs index a0cb5f731..b05ae2e8b 100644 --- a/src/shared/Core.Tests/GenericOAuthConfigTests.cs +++ b/src/shared/Core.Tests/GenericOAuthConfigTests.cs @@ -72,15 +72,11 @@ public void GenericOAuthConfig_TryGet_Gitea() var protocol = "https"; var host = "example.com"; var remoteUri = new Uri($"{protocol}://{host}"); - // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications - const string expectedClientId = "e90ee53c-94e2-48ac-9358-a874fb9e0662"; - // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#endpoints - const string authzEndpoint = "/login/oauth/authorize"; - const string tokenEndpoint = "/login/oauth/access_token"; + const string expectedClientId = GenericOAuthConfig.WellKnown.GiteaClientId; string[] expectedScopes = Array.Empty(); - var expectedRedirectUri = new Uri("http://127.0.0.1"); - var expectedAuthzEndpoint = new Uri(remoteUri, authzEndpoint); - var expectedTokenEndpoint = new Uri(remoteUri, tokenEndpoint); + var expectedRedirectUri = GenericOAuthConfig.WellKnown.LocalIPv4RedirectUri; + var expectedAuthzEndpoint = new Uri(remoteUri, GenericOAuthConfig.WellKnown.GiteaAuthzEndpoint); + var expectedTokenEndpoint = new Uri(remoteUri, GenericOAuthConfig.WellKnown.GiteaTokenEndpoint); var trace = new NullTrace(); var settings = new TestSettings diff --git a/src/shared/Core/GenericOAuthConfig.cs b/src/shared/Core/GenericOAuthConfig.cs index 09d21436f..fc0434b67 100644 --- a/src/shared/Core/GenericOAuthConfig.cs +++ b/src/shared/Core/GenericOAuthConfig.cs @@ -14,15 +14,14 @@ public static bool TryGet(ITrace trace, ISettings settings, InputArguments input Uri tokenEndpointUri = null; var remoteUri = input.GetRemoteUri(); - if (input.WwwAuth.Any(x => x.Contains("Basic realm=\"Gitea\""))) + if (input.WwwAuth.Any(x => x.Contains("Basic realm=\"Gitea\"", StringComparison.OrdinalIgnoreCase))) { trace.WriteLine($"Using universal Gitea OAuth configuration"); // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications - config.ClientId = "e90ee53c-94e2-48ac-9358-a874fb9e0662"; - // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#endpoints - authzEndpointUri = new Uri(remoteUri, "/login/oauth/authorize"); - tokenEndpointUri = new Uri(remoteUri, "/login/oauth/access_token"); - config.RedirectUri = new Uri("http://127.0.0.1"); + config.ClientId = WellKnown.GiteaClientId; + authzEndpointUri = new Uri(remoteUri, WellKnown.GiteaAuthzEndpoint); + tokenEndpointUri = new Uri(remoteUri, WellKnown.GiteaTokenEndpoint); + config.RedirectUri = WellKnown.LocalIPv4RedirectUri; } if (settings.TryGetSetting( @@ -158,5 +157,15 @@ public static bool TryGet(ITrace trace, ISettings settings, InputArguments input public string DefaultUserName { get; set; } public bool SupportsDeviceCode => Endpoints.DeviceAuthorizationEndpoint != null; + + public static class WellKnown + { + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications + public const string GiteaClientId = "e90ee53c-94e2-48ac-9358-a874fb9e0662"; + // https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#endpoints + public const string GiteaAuthzEndpoint = "/login/oauth/authorize"; + public const string GiteaTokenEndpoint = "/login/oauth/access_token"; + public static Uri LocalIPv4RedirectUri = new Uri("http://127.0.0.1"); + } } }