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

namespace GitCredentialManager.Authentication
{
public partial class JsonWebToken : IToken
{
public bool IsExpired
{
get
{
return Payload.Expiry != null && Payload.Expiry < DateTimeOffset.Now.ToUnixTimeSeconds();
}
}
public string Type => "Bearer";
public string Value { get; }

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

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


protected HeaderData Header { get; }
protected PayloadData Payload { get; }
protected string Signature { get; }

public JsonWebToken(string value, HeaderData header, PayloadData payload, string signature)
{
Value = value;
Header = header;
Payload = payload;
Signature = signature;
}


[JsonSerializable(typeof(HeaderData))]
private partial class HeaderContext : JsonSerializerContext { }

[JsonSerializable(typeof(PayloadData))]
private partial class PayloadContext : 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]), HeaderContext.Default.HeaderData);
if ("JWT".Equals(header.Type, StringComparison.OrdinalIgnoreCase))
{
var payload = JsonSerializer.Deserialize(Base64Url.DecodeFromChars(parts[1]), PayloadContext.Default.PayloadData);
token = new JsonWebToken(value, header, payload, parts.Length > 2 ? parts[2] : null);
return true;
}
}
}
catch { }

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

public static class Token
{
public static bool TryCreate(string value, out IToken token)
{
if (JsonWebToken.TryCreate(value, out JsonWebToken jwt))
{
token = jwt;
return true;
}
token = null;
return false;
}
}
}
18 changes: 13 additions & 5 deletions src/Core/GenericHostProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,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}).");
if (!token.IsExpired) {
// TODO: create credential data for token type and value
return new GitResponse(credential);
}
_context.Trace.WriteLine("Credential token is expired.");
}
else
{
_context.Trace.WriteLine("Existing credential found.");
return new GitResponse(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
Loading