Skip to content

use universal Gitea OAuth configuration #1442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions src/shared/Core.Tests/GenericOAuthConfigTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using GitCredentialManager.Tests.Objects;
using Xunit;

Expand All @@ -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";
Expand Down Expand Up @@ -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<string, string> {
{"protocol", protocol},
{"host", host},
});

bool result = GenericOAuthConfig.TryGet(trace, settings, input, out GenericOAuthConfig config);

Assert.True(result);
Assert.Equal(expectedClientId, config.ClientId);
Expand All @@ -57,5 +65,39 @@ 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}");
const string expectedClientId = GenericOAuthConfig.WellKnown.GiteaClientId;
string[] expectedScopes = Array.Empty<string>();
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
{
RemoteUri = remoteUri
};

var input = new InputArguments(new Dictionary<string, string> {
{"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);
}
}
}
2 changes: 1 addition & 1 deletion src/shared/Core/GenericHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public override async Task<ICredential> 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}");
Expand Down
53 changes: 43 additions & 10 deletions src/shared/Core/GenericOAuthConfig.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,55 @@
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\"", StringComparison.OrdinalIgnoreCase)))
{
trace.WriteLine($"Using universal Gitea OAuth configuration");
// https://docs.gitea.com/next/development/oauth2-provider?_highlight=oauth#pre-configured-applications
config.ClientId = WellKnown.GiteaClientId;
authzEndpointUri = new Uri(remoteUri, WellKnown.GiteaAuthzEndpoint);
tokenEndpointUri = new Uri(remoteUri, WellKnown.GiteaTokenEndpoint);
config.RedirectUri = WellKnown.LocalIPv4RedirectUri;
}

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;
Expand Down Expand Up @@ -74,12 +97,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;
Expand Down Expand Up @@ -134,5 +157,15 @@ public static bool TryGet(ITrace trace, ISettings settings, Uri remoteUri, out G
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");
}
}
}