Skip to content

Commit

Permalink
input: parse and surface wwwauth[] Git input args
Browse files Browse the repository at this point in the history
Add the new `wwwauth[]` credential property to the input arguments
surfaced to providers and commands.
  • Loading branch information
mjcheetham committed Jun 9, 2023
1 parent 6d44635 commit e104c18
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 24 deletions.
35 changes: 25 additions & 10 deletions src/shared/Core.Tests/InputArgumentsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@ public class InputArgumentsTests
[Fact]
public void InputArguments_Ctor_Null_ThrowsArgNullException()
{
Assert.Throws<ArgumentNullException>(() => new InputArguments(null));
Assert.Throws<ArgumentNullException>(() => new InputArguments((IDictionary<string, string>)null));
Assert.Throws<ArgumentNullException>(() => new InputArguments((IDictionary<string, IList<string>>)null));
}

[Fact]
public void InputArguments_CommonArguments_ValuePresent_ReturnsValues()
{
var dict = new Dictionary<string, string>
var dict = new Dictionary<string, IList<string>>
{
["protocol"] = "https",
["host"] = "example.com",
["path"] = "an/example/path",
["username"] = "john.doe",
["password"] = "password123"
["protocol"] = new[] { "https" },
["host"] = new[] { "example.com" },
["path"] = new[] { "an/example/path" },
["username"] = new[] { "john.doe" },
["password"] = new[] { "password123" },
["wwwauth"] = new[]
{
"basic realm=\"example.com\"",
"bearer authorize_uri=https://id.example.com p=1 q=0"
}
};

var inputArgs = new InputArguments(dict);
Expand All @@ -31,10 +37,16 @@ public void InputArguments_CommonArguments_ValuePresent_ReturnsValues()
Assert.Equal("an/example/path", inputArgs.Path);
Assert.Equal("john.doe", inputArgs.UserName);
Assert.Equal("password123", inputArgs.Password);
Assert.Equal(new[]
{
"basic realm=\"example.com\"",
"bearer authorize_uri=https://id.example.com p=1 q=0"
},
inputArgs.WwwAuth);
}

[Fact]
public void InputArguments_CommonArguments_ValueMissing_ReturnsNull()
public void InputArguments_CommonArguments_ValueMissing_ReturnsNullOrEmptyCollection()
{
var dict = new Dictionary<string, string>();

Expand All @@ -45,20 +57,23 @@ public void InputArguments_CommonArguments_ValueMissing_ReturnsNull()
Assert.Null(inputArgs.Path);
Assert.Null(inputArgs.UserName);
Assert.Null(inputArgs.Password);
Assert.Empty(inputArgs.WwwAuth);
}

[Fact]
public void InputArguments_OtherArguments()
{
var dict = new Dictionary<string, string>
var dict = new Dictionary<string, IList<string>>
{
["foo"] = "bar"
["foo"] = new[] { "bar" },
["multi"] = new[] { "val1", "val2", "val3" },
};

var inputArgs = new InputArguments(dict);

Assert.Equal("bar", inputArgs["foo"]);
Assert.Equal("bar", inputArgs.GetArgumentOrDefault("foo"));
Assert.Equal(new[] { "val1", "val2", "val3" }, inputArgs.GetMultiArgumentOrDefault("multi"));
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion src/shared/Core/Commands/GitCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal async Task ExecuteAsync()

// Parse standard input arguments
// git-credential treats the keys as case-sensitive; so should we.
IDictionary<string, string> inputDict = await Context.Streams.In.ReadDictionaryAsync(StringComparer.Ordinal);
IDictionary<string, IList<string>> inputDict = await Context.Streams.In.ReadMultiDictionaryAsync(StringComparer.Ordinal);
var input = new InputArguments(inputDict);

// Validate minimum arguments are present
Expand Down
46 changes: 39 additions & 7 deletions src/shared/Core/InputArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@ namespace GitCredentialManager
/// </remarks>
public class InputArguments
{
private readonly IReadOnlyDictionary<string, string> _dict;
private readonly IReadOnlyDictionary<string, IList<string>> _dict;

public InputArguments(IDictionary<string, string> dict)
{
if (dict == null)
{
throw new ArgumentNullException(nameof(dict));
}
EnsureArgument.NotNull(dict, nameof(dict));

// Transform input from 1:1 to 1:n and store as readonly
_dict = new ReadOnlyDictionary<string, IList<string>>(
dict.ToDictionary(x => x.Key, x => (IList<string>)new[] { x.Value })
);
}

public InputArguments(IDictionary<string, IList<string>> dict)
{
EnsureArgument.NotNull(dict, nameof(dict));

// Wrap the dictionary internally as readonly
_dict = new ReadOnlyDictionary<string, string>(dict);
_dict = new ReadOnlyDictionary<string, IList<string>>(dict);
}

#region Common Arguments
Expand All @@ -35,6 +42,7 @@ public InputArguments(IDictionary<string, string> dict)
public string Path => GetArgumentOrDefault("path");
public string UserName => GetArgumentOrDefault("username");
public string Password => GetArgumentOrDefault("password");
public IList<string> WwwAuth => GetMultiArgumentOrDefault("wwwauth");

#endregion

Expand All @@ -50,9 +58,33 @@ public string GetArgumentOrDefault(string key)
return TryGetArgument(key, out string value) ? value : null;
}

public IList<string> GetMultiArgumentOrDefault(string key)
{
return TryGetMultiArgument(key, out IList<string> values) ? values : Array.Empty<string>();
}

public bool TryGetArgument(string key, out string value)
{
return _dict.TryGetValue(key, out value);
if (_dict.TryGetValue(key, out IList<string> values))
{
value = values.FirstOrDefault();
return value != null;
}

value = null;
return false;
}

public bool TryGetMultiArgument(string key, out IList<string> value)
{
if (_dict.TryGetValue(key, out IList<string> values))
{
value = values;
return true;
}

value = null;
return false;
}

public bool TryGetHostAndPort(out string host, out int? port)
Expand Down
13 changes: 7 additions & 6 deletions src/shared/Core/Trace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,24 +217,25 @@ public void WriteDictionarySecrets<TKey, TValue>(
bool isSecretEntry = !(secretKeys is null) &&
secretKeys.Contains(entry.Key, keyComparer ?? EqualityComparer<TKey>.Default);

void WriteSecretLine(object value)
void WriteSecretLine(string keySuffix, object value)
{
var message = isSecretEntry && !IsSecretTracingEnabled
? $"\t{entry.Key}={SecretMask}"
: $"\t{entry.Key}={value}";
? $"\t{entry.Key}{keySuffix}={SecretMask}"
: $"\t{entry.Key}{keySuffix}={value}";
WriteLine(message, filePath, lineNumber, memberName);
}

if (entry.Value is IEnumerable<string> values)
{
foreach (string value in values)
List<string> valueList = values.ToList();
foreach (string value in valueList)
{
WriteSecretLine(value);
WriteSecretLine(valueList.Count > 1 ? "[]" : string.Empty, value);
}
}
else
{
WriteSecretLine(entry.Value);
WriteSecretLine(string.Empty, entry.Value);
}
}
}
Expand Down

0 comments on commit e104c18

Please sign in to comment.