Skip to content

Commit

Permalink
Siimplyfing methods of Decoder and Validator (#233)
Browse files Browse the repository at this point in the history
* Replacing methods Validate(string, string, string) and Validate(string, string, string[]) with single Validate(string, string, params string[])
* Bumping version to 6.0.0-beta1
  • Loading branch information
abatishchev authored Dec 2, 2019
1 parent 5d305d5 commit f3d6e3f
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 104 deletions.
22 changes: 14 additions & 8 deletions src/JWT/Builder/JwtBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,13 @@ private void TryCreateValidator()
private void EnsureCanBuild()
{
if (!CanBuild())
throw new InvalidOperationException("Can't build a token. Check if you have call all of the followng methods:\r\n" +
$"-{nameof(WithAlgorithm)}" + Environment.NewLine +
$"-{nameof(WithSerializer)}" + Environment.NewLine +
$"-{nameof(WithUrlEncoder)}.");
{
throw new InvalidOperationException(
"Can't build a token. Check if you have call all of the following methods:" +Environment.NewLine +
$"-{nameof(WithAlgorithm)}" + Environment.NewLine +
$"-{nameof(WithSerializer)}" + Environment.NewLine +
$"-{nameof(WithUrlEncoder)}.");
}

if (!HasOnlyOneSecret())
throw new InvalidOperationException("You can't provide more than one secret to use for encoding.");
Expand All @@ -315,10 +318,13 @@ private void EnsureCanBuild()
private void EnsureCanDecode()
{
if (!CanDecode())
throw new InvalidOperationException("Can't decode a token. Check if you have call all of the following methods:" + Environment.NewLine +
$"-{nameof(WithSerializer)}" + Environment.NewLine +
$"-{nameof(WithValidator)}" + Environment.NewLine +
$"-{nameof(WithUrlEncoder)}.");
{
throw new InvalidOperationException(
"Can't decode a token. Check if you have call all of the following methods:" + Environment.NewLine +
$"-{nameof(WithSerializer)}" + Environment.NewLine +
$"-{nameof(WithValidator)}" + Environment.NewLine +
$"-{nameof(WithUrlEncoder)}.");
}
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/JWT/IJwtDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public interface IJwtDecoder
/// <param name="keys">The keys bytes provided which one of them was used to sign the JWT</param>
/// <param name="verify">Whether to verify the signature (default is true)</param>
/// <returns>A string containing the JSON payload</returns>
string Decode(string token, IReadOnlyCollection<byte[]> keys, bool verify);
string Decode(string token, byte[][] keys, bool verify);

#endregion

Expand Down Expand Up @@ -104,7 +104,7 @@ public interface IJwtDecoder
/// <returns>An object representing the payload</returns>
/// <exception cref="SignatureVerificationException">Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm</exception>
/// <exception cref="TokenExpiredException">Thrown if the verify parameter was true and the token has an expired exp claim</exception>
IDictionary<string, object> DecodeToObject(string token, IReadOnlyCollection<byte[]> keys, bool verify);
IDictionary<string, object> DecodeToObject(string token, byte[][] keys, bool verify);

#endregion

Expand Down Expand Up @@ -164,7 +164,7 @@ public interface IJwtDecoder
/// <returns>An object representing the payload</returns>
/// <exception cref="SignatureVerificationException">Thrown if the verify parameter was true and the signature was NOT valid or if the JWT was signed with an unsupported algorithm</exception>
/// <exception cref="TokenExpiredException">Thrown if the verify parameter was true and the token has an expired exp claim</exception>
T DecodeToObject<T>(string token, IReadOnlyCollection<byte[]> keys, bool verify);
T DecodeToObject<T>(string token, byte[][] keys, bool verify);

#endregion
}
Expand Down
12 changes: 1 addition & 11 deletions src/JWT/IJwtValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
/// </summary>
public interface IJwtValidator
{
/// <summary>
/// Given the JWT, verifies its signature correctness.
/// </summary>
/// <param name="payloadJson">>An arbitrary payload (already serialized to JSON)</param>
/// <param name="decodedCrypto">Decoded body</param>
/// <param name="decodedSignature">Decoded signature</param>
/// <exception cref="SignatureVerificationException">The signature is invalid</exception>
/// <exception cref="TokenExpiredException">The token has expired</exception>
void Validate(string payloadJson, string decodedCrypto, string decodedSignature);

/// <summary>
/// Given the JWT, verifies its signatures correctness.
/// </summary>
Expand All @@ -23,6 +13,6 @@ public interface IJwtValidator
/// <param name="decodedSignatures">Decoded signatures</param>
/// <exception cref="SignatureVerificationException">The signature is invalid</exception>
/// <exception cref="TokenExpiredException">The token has expired</exception>
void Validate(string payloadJson, string decodedCrypto, string[] decodedSignatures);
void Validate(string payloadJson, string decodedCrypto, params string[] decodedSignatures);
}
}
6 changes: 3 additions & 3 deletions src/JWT/JWT.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

<Version>5.3.1</Version>
<FileVersion>5.0.0.0</FileVersion>
<AssemblyVersion>5.0.0.0</AssemblyVersion>
<Version>6.0.0-beta1</Version>
<FileVersion>6.0.0.0</FileVersion>
<AssemblyVersion>6.0.0.0</AssemblyVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
58 changes: 11 additions & 47 deletions src/JWT/JwtDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ public string Decode(string token, byte[] key, bool verify)
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public string Decode(string token, IReadOnlyCollection<byte[]> keys, bool verify)
public string Decode(string token, byte[][] keys, bool verify)
{
if (String.IsNullOrWhiteSpace(token))
throw new ArgumentException(nameof(token));
if (keys is null)
throw new ArgumentNullException(nameof(keys));
if (keys.Count == 0 || !AllKeysHaveValues(keys))
if (keys.Length == 0 || !AllKeysHaveValues(keys))
throw new ArgumentOutOfRangeException(nameof(keys));

if (verify)
Expand Down Expand Up @@ -152,7 +152,7 @@ public IDictionary<string, object> DecodeToObject(string token, byte[] key, bool
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public IDictionary<string, object> DecodeToObject(string token, IReadOnlyCollection<byte[]> keys, bool verify) =>
public IDictionary<string, object> DecodeToObject(string token, byte[][] keys, bool verify) =>
DecodeToObject<Dictionary<string, object>>(token, keys, verify);

/// <inheritdoc />
Expand Down Expand Up @@ -193,75 +193,38 @@ public T DecodeToObject<T>(string token, byte[] key, bool verify)
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public T DecodeToObject<T>(string token, IReadOnlyCollection<byte[]> keys, bool verify)
public T DecodeToObject<T>(string token, byte[][] keys, bool verify)
{
var payload = Decode(token, keys, verify);
return _jsonSerializer.Deserialize<T>(payload);
}

/// <summary>
/// Prepares data before calling <see cref="IJwtValidator.Validate(string,string,string)" />
/// Prepares data before calling <see cref="IJwtValidator.Validate" />
/// </summary>
/// <param name="parts">The array representation of a JWT</param>
/// <param name="key">The key that was used to sign the JWT</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public void Validate(string[] parts, byte[] key) =>
public void Validate(string[] parts, params byte[] key) =>
Validate(new JwtParts(parts), key);

/// <summary>
/// Prepares data before calling <see cref="IJwtValidator.Validate(string,string,string)" />
/// </summary>
/// <param name="jwt">The JWT parts</param>
/// <param name="key">The key that was used to sign the JWT</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public void Validate(JwtParts jwt, byte[] key)
{
if (jwt is null)
throw new ArgumentNullException(nameof(jwt));
if (key is null)
throw new ArgumentNullException(nameof(key));
if (key.Length == 0)
throw new ArgumentOutOfRangeException(nameof(key));

var crypto = _urlEncoder.Decode(jwt.Signature);
var decodedCrypto = Convert.ToBase64String(crypto);

var headerJson = GetString(_urlEncoder.Decode(jwt.Header));
var headerData = _jsonSerializer.Deserialize<Dictionary<string, object>>(headerJson);

var payload = jwt.Payload;
var payloadJson = GetString(_urlEncoder.Decode(payload));

var bytesToSign = GetBytes(String.Concat(jwt.Header, ".", payload));

var algName = (string)headerData["alg"];
var alg = _algFactory.Create(algName);

var signatureData = alg.Sign(key, bytesToSign);
var decodedSignature = Convert.ToBase64String(signatureData);

_jwtValidator.Validate(payloadJson, decodedCrypto, decodedSignature);
}

/// <summary>
/// Prepares data before calling <see cref="IJwtValidator.Validate(string,string,string[])" />
/// Prepares data before calling <see cref="IJwtValidator.Validate" />
/// </summary>
/// <param name="jwt">The JWT parts</param>
/// <param name="keys">The keys provided which one of them was used to sign the JWT</param>
/// <exception cref="ArgumentNullException" />
/// <exception cref="ArgumentOutOfRangeException" />
/// <exception cref="FormatException" />
public void Validate(JwtParts jwt, IReadOnlyCollection<byte[]> keys)
public void Validate(JwtParts jwt, params byte[][] keys)
{
if (jwt is null)
throw new ArgumentNullException(nameof(jwt));
if (keys is null)
throw new ArgumentNullException(nameof(keys));
if (keys.Count == 0 || !AllKeysHaveValues(keys))
if (keys.Length == 0 || !AllKeysHaveValues(keys))
throw new ArgumentOutOfRangeException(nameof(keys));

var crypto = _urlEncoder.Decode(jwt.Signature);
Expand All @@ -281,10 +244,11 @@ public void Validate(JwtParts jwt, IReadOnlyCollection<byte[]> keys)
var decodedSignatures = keys.Select(key => alg.Sign(key, bytesToSign))
.Select(sd => Convert.ToBase64String(sd))
.ToArray();

_jwtValidator.Validate(payloadJson, decodedCrypto, decodedSignatures);
}

private static bool AllKeysHaveValues(IEnumerable<byte[]> keys) =>
keys.All(key => key.Any());
}
}
}
35 changes: 4 additions & 31 deletions src/JWT/JwtValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,7 @@ public JwtValidator(IJsonSerializer jsonSerializer, IDateTimeProvider dateTimePr
/// <inheritdoc />
/// <exception cref="ArgumentException" />
/// <exception cref="SignatureVerificationException" />
public void Validate(string payloadJson, string decodedCrypto, string decodedSignature)
{
var ex = GetValidationException(payloadJson, decodedCrypto, decodedSignature);
if (ex != null)
throw ex;
}

/// <inheritdoc />
/// <exception cref="ArgumentException" />
/// <exception cref="SignatureVerificationException" />
public void Validate(string payloadJson, string decodedCrypto, string[] decodedSignatures)
public void Validate(string payloadJson, string decodedCrypto, params string[] decodedSignatures)
{
var ex = GetValidationException(payloadJson, decodedCrypto, decodedSignatures);
if (ex != null)
Expand Down Expand Up @@ -73,24 +63,7 @@ public bool TryValidate(string payloadJson, string decodedCrypto, string[] decod
return ex is null;
}

private Exception GetValidationException(string payloadJson, string decodedCrypto, string decodedSignature)
{
if (String.IsNullOrWhiteSpace(payloadJson))
return new ArgumentException(nameof(payloadJson));

if (String.IsNullOrWhiteSpace(decodedCrypto))
return new ArgumentException(nameof(decodedCrypto));

if (String.IsNullOrWhiteSpace(decodedSignature))
return new ArgumentException(nameof(decodedSignature));

if (!CompareCryptoWithSignature(decodedCrypto, decodedSignature))
return new SignatureVerificationException(decodedCrypto, decodedSignature);

return GetValidationException(payloadJson);
}

private Exception GetValidationException(string payloadJson, string decodedCrypto, string[] decodedSignatures)
private Exception GetValidationException(string payloadJson, string decodedCrypto, params string[] decodedSignatures)
{
if (String.IsNullOrWhiteSpace(payloadJson))
return new ArgumentException(nameof(payloadJson));
Expand Down Expand Up @@ -123,7 +96,7 @@ private static bool AreAllDecodedSignaturesNullOrWhiteSpace(IEnumerable<string>
private static bool IsAnySignatureValid(string decodedCrypto, IEnumerable<string> decodedSignatures) =>
decodedSignatures.Any(decodedSignature => CompareCryptoWithSignature(decodedCrypto, decodedSignature));

/// <remarks>In the future this method can be opened for extension so made protected virtual</remarks>
/// <remarks>In the future this method can be opened for extension thus made protected virtual</remarks>
private static bool CompareCryptoWithSignature(string decodedCrypto, string decodedSignature)
{
if (decodedCrypto.Length != decodedSignature.Length)
Expand All @@ -147,7 +120,7 @@ private static bool CompareCryptoWithSignature(string decodedCrypto, string deco
/// <remarks>See https://tools.ietf.org/html/rfc7515#section-4.1.4</remarks>
/// <exception cref="SignatureVerificationException" />
/// <exception cref="TokenExpiredException" />
private static Exception ValidateExpClaim(IDictionary<string, object> payloadData, double secondsSinceEpoch)
private static Exception ValidateExpClaim(IReadOnlyDictionary<string, object> payloadData, double secondsSinceEpoch)
{
if (!payloadData.TryGetValue("exp", out var expObj))
return null;
Expand Down
2 changes: 1 addition & 1 deletion src/JWT/TokenExpiredException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public TokenExpiredException(string message)
/// <summary>
/// The payload.
/// </summary>
public IDictionary<string, object> PayloadData
public IReadOnlyDictionary<string, object> PayloadData
{
get => GetOrDefault<Dictionary<string, object>>(PayloadDataKey);
internal set => this.Data.Add(PayloadDataKey, value);
Expand Down

0 comments on commit f3d6e3f

Please sign in to comment.