Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected override async Task HandleChallengeAsync(AuthenticationProperties prop
var nonceGenerator = Options.NonceGenerator;
var staleNonce = Context.Items[DigestFields.Stale] as string ?? "false";
AuthenticationHandlerFeature.Set(await HandleAuthenticateOnceSafeAsync().ConfigureAwait(false), Context); // so annoying that Microsoft does not propagate AuthenticateResult properly - other have noticed as well: https://github.com/dotnet/aspnetcore/issues/44100
Decorator.Enclose(Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale=\"{staleNonce}\", algorithm=\"{DigestAuthenticationMiddleware.ParseAlgorithm(Options.Algorithm)}\""));
Decorator.Enclose(Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={DigestAuthenticationMiddleware.ParseAlgorithm(Options.Algorithm)}"));
await base.HandleChallengeAsync(properties).ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ await Decorator.Enclose(context).InvokeUnauthorizedExceptionAsync(Options, princ
var nonceSecret = Options.NonceSecret;
var nonceGenerator = Options.NonceGenerator;
var staleNonce = dc.Items[DigestFields.Stale] as string ?? "false";
Decorator.Enclose(dc.Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale=\"{staleNonce}\", algorithm=\"{ParseAlgorithm(Options.Algorithm)}\""));
Decorator.Enclose(dc.Response.Headers).TryAdd(HeaderNames.WWWAuthenticate, string.Create(CultureInfo.InvariantCulture, $"{DigestAuthorizationHeader.Scheme} realm=\"{Options.Realm}\", qop=\"auth, auth-int\", nonce=\"{nonceGenerator(DateTime.UtcNow, etag, nonceSecret())}\", opaque=\"{opaqueGenerator()}\", stale={staleNonce}, algorithm={ParseAlgorithm(Options.Algorithm)}"));
}).ConfigureAwait(false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class DigestAuthorizationHeader : AuthorizationHeader
public static DigestAuthorizationHeader Create(string authorizationHeader)
{
Validator.ThrowIfNullOrWhitespace(authorizationHeader);
return new DigestAuthorizationHeader().Parse(authorizationHeader, o => o.CredentialsDelimiter = " ") as DigestAuthorizationHeader;
return new DigestAuthorizationHeader().Parse(authorizationHeader, o => o.CredentialsDelimiter = ", ") as DigestAuthorizationHeader;
}

/// <summary>
Expand All @@ -39,6 +39,33 @@ private DigestAuthorizationHeader() : base(Scheme)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="DigestAuthorizationHeader"/> class.
/// </summary>
/// <param name="realm">The realm/credential scope that defines the remote resource.</param>
/// <param name="nonce">The unique server generated string.</param>
/// <param name="opaque">The string of data specified by the server.</param>
/// <param name="algorithm">The algorithm used to produce the digest and an unkeyed digest.</param>
/// <param name="userName">The username of the specified <paramref name="realm"/>.</param>
/// <param name="uri">The effective request URI.</param>
/// <param name="nc">The hexadecimal count of the number of requests the client has sent with the <paramref name="nonce"/> value.</param>
/// <param name="cNonce">The unique client generated string.</param>
/// <param name="qop">The "quality of protection" the client has applied to the message.</param>
/// <param name="response">The computed response which proves that the user knows a password.</param>
public DigestAuthorizationHeader(string realm, string nonce, string opaque, string algorithm, string userName, string uri, string nc, string cNonce, string qop, string response) : base(Scheme)
{
Realm = realm;
Nonce = nonce;
Opaque = opaque;
Algorithm = algorithm;
UserName = userName;
Uri = uri;
NC = nc;
CNonce = cNonce;
Qop = qop;
Response = response;
}

/// <summary>
/// Initializes a new instance of the <see cref="DigestAuthorizationHeader"/> class.
/// </summary>
Expand All @@ -53,6 +80,7 @@ private DigestAuthorizationHeader() : base(Scheme)
/// <param name="cNonce">The unique client generated string.</param>
/// <param name="qop">The "quality of protection" the client has applied to the message.</param>
/// <param name="response">The computed response which proves that the user knows a password.</param>
[Obsolete("This constructor is obsolete and will be removed in a future version. Use the 'DigestAuthorizationHeader' constructor without the 'stale' parameter instead.")]
public DigestAuthorizationHeader(string realm, string nonce, string opaque, string stale, string algorithm, string userName, string uri, string nc, string cNonce, string qop, string response) : base(Scheme)
{
Realm = realm;
Expand Down Expand Up @@ -132,6 +160,7 @@ public DigestAuthorizationHeader(string realm, string nonce, string opaque, stri
/// Gets the case-insensitive flag indicating if the previous request from the client was rejected because the <see cref="Nonce"/> value was stale.
/// </summary>
/// <value>The case-insensitive flag indicating if the previous request from the client was rejected because the <see cref="Nonce"/> value was stale.</value>
[Obsolete("This property is obsolete and will be removed in a future version.")]
public string Stale { get; }

/// <summary>
Expand All @@ -151,8 +180,7 @@ protected override AuthorizationHeader ParseCore(IReadOnlyDictionary<string, str
valid |= credentials.TryGetValue(DigestFields.QualityOfProtection, out var qop);
valid |= credentials.TryGetValue(DigestFields.ClientNonce, out var cnonce);
valid |= credentials.TryGetValue(DigestFields.NonceCount, out var nc);
valid |= credentials.TryGetValue(DigestFields.Stale, out var stale);
return valid ? new DigestAuthorizationHeader(realm, nonce, opaque, stale, algorithm, userName, uri, nc, cnonce, qop, response) : null;
return valid ? new DigestAuthorizationHeader(realm, nonce, opaque, algorithm, userName, uri, nc, cnonce, qop, response) : null;
}

/// <summary>
Expand All @@ -166,19 +194,25 @@ public override string ToString()
AppendField(sb, DigestFields.Realm, Realm);
AppendField(sb, DigestFields.Nonce, Nonce);
AppendField(sb, DigestFields.DigestUri, Uri);
AppendField(sb, DigestFields.QualityOfProtection, Qop);
AppendField(sb, DigestFields.NonceCount, NC);
AppendField(sb, DigestFields.QualityOfProtection, Qop, false);
AppendField(sb, DigestFields.NonceCount, NC, false);
AppendField(sb, DigestFields.ClientNonce, CNonce);
AppendField(sb, DigestFields.Response, Response);
AppendField(sb, DigestFields.Opaque, Opaque);
AppendField(sb, DigestFields.Stale, Stale);
AppendField(sb, DigestFields.Algorithm, Algorithm);
return sb.ToString();
AppendField(sb, DigestFields.Algorithm, Algorithm, false);
return sb.ToString().TrimEnd(',');
}

private static void AppendField(StringBuilder sb, string fn, string fv, bool useQuotedStringSyntax = true)
{
if (!string.IsNullOrWhiteSpace(fv)) { sb.Append(CultureInfo.InvariantCulture, $" {fn}={Parse(fv, useQuotedStringSyntax)},"); }
}

private static void AppendField(StringBuilder sb, string fn, string fv)
private static string Parse(string value, bool useQuotedStringSyntax)
{
if (!string.IsNullOrWhiteSpace(fv)) { sb.Append(CultureInfo.InvariantCulture, $" {fn}=\"{fv}\""); }
return useQuotedStringSyntax
? $"\"{value}\""
: value;
}
}
}
1 change: 1 addition & 0 deletions src/Cuemon.AspNetCore.Authentication/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design to support the Digest protocol.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Authentication.Digest.DigestAuthorizationHeader.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String)")]
[assembly: SuppressMessage("Performance", "CA1847:Use char literal for a single character lookup", Justification = "Not supported in .NET Standard 2.0 (and not an issue with an extra pico-second).", Scope = "member", Target = "~M:Cuemon.AspNetCore.Authentication.Basic.BasicAuthorizationHeader.#ctor(System.String,System.String)")]
[assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "Intentional as these embark on IDecorator.", Scope = "namespace", Target = "~N:Cuemon.AspNetCore.Authentication")]
[assembly: SuppressMessage("Major Code Smell", "S107:Methods should not have too many parameters", Justification = "By design to support the Digest protocol.", Scope = "member", Target = "~M:Cuemon.AspNetCore.Authentication.Digest.DigestAuthorizationHeader.#ctor(System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String)")]
Loading