Skip to content
Open
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
68 changes: 68 additions & 0 deletions src/Core/Authentication/JsonWebToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace GitCredentialManager.Authentication
{
public partial class JsonWebToken
{
public static readonly string Type = "JWT";

public long? Expiry { get; }
public string Value { get; }

class Header
{
[JsonRequired]
[JsonInclude]
[JsonPropertyName("typ")]
public string Type { get; internal set; }
}

class Payload
{
[JsonInclude]
[JsonPropertyName("exp")]
public long? Expiry { get; internal set; }
}

JsonWebToken(long? expiry, string value)
{
Expiry = expiry;
Value = value;
}


[JsonSerializable(typeof(Header))]
private partial class HeaderDto : JsonSerializerContext { }

[JsonSerializable(typeof(Payload))]
private partial class PayloadDto : JsonSerializerContext { }


public static bool TryCreate(string value, out JsonWebToken token)
{
try
{
// elements of JWT structure "<header>.<payload>.<signature>"
var parts = value.Split('.');
if (parts.Length == 2 || parts.Length == 3)
{
var header = JsonSerializer.Deserialize(Base64Url.DecodeFromChars(parts[0]), HeaderDto.Default.Header);
if (Type.Equals(header.Type, StringComparison.OrdinalIgnoreCase))
{
var payload = JsonSerializer.Deserialize(Base64Url.DecodeFromChars(parts[1]), PayloadDto.Default.Payload);
token = new JsonWebToken(payload.Expiry, value);
return true;
}
}
}
catch { }

// invalid token data on content mismatch or deserializer exception
token = null;
return false;
}
}
}
32 changes: 32 additions & 0 deletions src/Core/Authentication/Token.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace GitCredentialManager.Authentication
{
public interface IToken
{
string Type { get; }
string Value { get; }
long? Expiry { get; }
}

public static class Token
{
protected class Jwt(string value, long? expiry) : IToken
{
public string Type => JsonWebToken.Type;
public string Value => value;
public long? Expiry => expiry;

}

public static bool TryCreate(string value, out IToken token)
{
if (JsonWebToken.TryCreate(value, out JsonWebToken jwt))
{
token = new Jwt(value, jwt.Expiry);
return true;
}

token = null;
return false;
}
}
}
29 changes: 20 additions & 9 deletions src/Core/GenericHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading.Tasks;
using GitCredentialManager.Authentication;
using GitCredentialManager.Authentication.OAuth;
using GitCredentialManager.Tty;

namespace GitCredentialManager
{
Expand Down Expand Up @@ -75,17 +74,25 @@ public async Task<GitResponse> GetCredentialAsync(GitRequest request)
if (credential == null)
{
_context.Trace.WriteLine("No existing credentials found.");

// No existing credential was found, create a new one
_context.Trace.WriteLine("Creating new credential...");
return await GenerateCredentialAsync(request);
}
else if (Token.TryCreate(credential.Password, out var token))
{
_context.Trace.WriteLine($"Existing token found (type={token.Type}).");
// comparing null and long will always be false
if (!(token.Expiry < DateTimeOffset.Now.ToUnixTimeSeconds())) {
return GitResponse.Ok(new GitCredential(credential.Account, token.Value), isEphemeral: token.Expiry != null, authtype: null);
}
_context.Trace.WriteLine("Credential token is expired.");
}
else
{
_context.Trace.WriteLine("Existing credential found.");
return GitResponse.Ok(credential);
}

return new GitResponse(credential);
// No valid credential was found, create a new one
_context.Trace.WriteLine("Creating new credential...");
return await GenerateCredentialAsync(request);
}

public Task StoreCredentialAsync(GitRequest request)
Expand Down Expand Up @@ -159,9 +166,13 @@ public async Task<GitResponse> GenerateCredentialAsync(GitRequest request)
_context.Trace.WriteLine($"\tUseAuthHeader = {oauthConfig.UseAuthHeader}");
_context.Trace.WriteLine($"\tDefaultUserName = {oauthConfig.DefaultUserName}");

return new GitResponse(
await GetOAuthAccessToken(uri, request.UserName, oauthConfig)
);
var credential = await GetOAuthAccessToken(uri, request.UserName, oauthConfig);
if (Token.TryCreate(credential.Password, out IToken token))
{
return GitResponse.Ok(new GitCredential(credential.Account, token.Value), isEphemeral: token.Expiry != null, authtype: null);
}

return new GitResponse(credential);
}

// Try detecting WIA for this remote, if permitted and possible (http(s) required for probing WIA)
Expand Down
Loading