diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
index 763ef11d3..2e1a80889 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationDefaults.cs
@@ -9,14 +9,14 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public static class OpenIdConnectAuthenticationDefaults
{
///
- /// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationScheme
+ /// Constant used to identify state in openIdConnect protocol message.
///
- public const string AuthenticationScheme = "OpenIdConnect";
+ public const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
///
- /// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName
+ /// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationScheme.
///
- public const string CookiePrefix = ".AspNet.OpenIdConnect.";
+ public const string AuthenticationScheme = "OpenIdConnect";
///
/// The default value for OpenIdConnectAuthenticationOptions.Caption.
@@ -24,18 +24,23 @@ public static class OpenIdConnectAuthenticationDefaults
public const string Caption = "OpenIdConnect";
///
- /// The prefix used to for the a nonce in the cookie
+ /// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName.
+ ///
+ public const string CookiePrefix = ".AspNet.OpenIdConnect.";
+
+ ///
+ /// The prefix used to for the a nonce in the cookie.
///
public const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
///
- /// The property for the RedirectUri that was used when asking for a 'authorizationCode'
+ /// The property for the RedirectUri that was used when asking for a 'authorizationCode'.
///
- public const string RedirectUriUsedForCodeKey = "OpenIdConnect.Code.RedirectUri";
+ public const string RedirectUriForCodePropertiesKey = "OpenIdConnect.Code.RedirectUri";
///
- /// Constant used to identify state in openIdConnect protocal message
+ /// Constant used to identify userstate inside AuthenticationProperties that have been serialized in the 'state' parameter.
///
- public const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
+ public const string UserstatePropertiesKey = "OpenIdConnect.Userstate";
}
}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
index 7e97628e1..2c6d50044 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationHandler.cs
@@ -23,6 +23,7 @@ public class OpenIdConnectAuthenticationHandler : AuthenticationHandler(Context, Options)
- {
- ProtocolMessage = openIdConnectMessage
- };
-
- await Options.Notifications.RedirectToIdentityProvider(notification);
-
- if (!notification.HandledResponse)
+ if (Options.Notifications.RedirectToIdentityProvider != null)
{
- var redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
- if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
+ var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification(Context, Options, message);
+ await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
+ if (redirectToIdentityProviderNotification.HandledResponse)
{
- Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
+ Logger.LogVerbose(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
+ return;
}
+ else if (redirectToIdentityProviderNotification.Skipped)
+ {
+ Logger.LogVerbose(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
+ return;
+ }
+
+ message = redirectToIdentityProviderNotification.ProtocolMessage;
+ }
- Response.Redirect(redirectUri);
+ var redirectUri = message.CreateLogoutRequestUrl();
+ if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
+ {
+ Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
}
+
+ Response.Redirect(redirectUri);
}
}
@@ -143,7 +152,7 @@ protected override async Task ApplyResponseChallengeAsync()
properties = new AuthenticationProperties(ChallengeContext.Properties);
}
- if (!string.IsNullOrWhiteSpace(properties.RedirectUri))
+ if (!string.IsNullOrEmpty(properties.RedirectUri))
{
Logger.LogDebug(Resources.OIDCH_0030_Using_Properties_RedirectUri, properties.RedirectUri);
}
@@ -153,15 +162,15 @@ protected override async Task ApplyResponseChallengeAsync()
properties.RedirectUri = CurrentUri;
}
- if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
+ if (!string.IsNullOrEmpty(Options.RedirectUri))
{
Logger.LogDebug(Resources.OIDCH_0031_Using_Options_RedirectUri, Options.RedirectUri);
}
// When redeeming a 'code' for an AccessToken, this value is needed
- if (!string.IsNullOrWhiteSpace(Options.RedirectUri))
+ if (!string.IsNullOrEmpty(Options.RedirectUri))
{
- properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey, Options.RedirectUri);
+ properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, Options.RedirectUri);
}
if (_configuration == null && Options.ConfigurationManager != null)
@@ -174,18 +183,46 @@ protected override async Task ApplyResponseChallengeAsync()
ClientId = Options.ClientId,
IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty,
RedirectUri = Options.RedirectUri,
- // [brentschmaltz] - this should be a property on RedirectToIdentityProviderNotification not on the OIDCMessage.
+ // [brentschmaltz] - #215 this should be a property on RedirectToIdentityProviderNotification not on the OIDCMessage.
RequestType = OpenIdConnectRequestType.AuthenticationRequest,
Resource = Options.Resource,
ResponseMode = Options.ResponseMode,
ResponseType = Options.ResponseType,
- Scope = Options.Scope,
- State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + UrlEncoder.UrlEncode(Options.StateDataFormat.Protect(properties))
+ Scope = Options.Scope
};
if (Options.ProtocolValidator.RequireNonce)
{
message.Nonce = Options.ProtocolValidator.GenerateNonce();
+ }
+
+ if (Options.Notifications.RedirectToIdentityProvider != null)
+ {
+ var redirectToIdentityProviderNotification =
+ new RedirectToIdentityProviderNotification(Context, Options, message);
+
+ await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
+ if (redirectToIdentityProviderNotification.HandledResponse)
+ {
+ Logger.LogVerbose(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
+ return;
+ }
+ else if (redirectToIdentityProviderNotification.Skipped)
+ {
+ Logger.LogVerbose(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(redirectToIdentityProviderNotification.ProtocolMessage.State))
+ {
+ properties.Items[OpenIdConnectAuthenticationDefaults.UserstatePropertiesKey] = redirectToIdentityProviderNotification.ProtocolMessage.State;
+ }
+
+ message = redirectToIdentityProviderNotification.ProtocolMessage;
+ }
+
+ if (!string.IsNullOrEmpty(message.Nonce))
+ {
if (Options.NonceCache != null)
{
if (!Options.NonceCache.TryAddNonce(message.Nonce))
@@ -200,29 +237,16 @@ protected override async Task ApplyResponseChallengeAsync()
}
}
- var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification(Context, Options)
- {
- ProtocolMessage = message
- };
-
- await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
- if (redirectToIdentityProviderNotification.HandledResponse)
- {
- Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
- return;
- }
- else if (redirectToIdentityProviderNotification.Skipped)
- {
- Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
- return;
- }
-
- var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
+ // If 'OpenIdConnectMessage.State' is null, then the user never set it in the notification. Just set the state
+ message.State = Options.StateDataFormat.Protect(properties);
+ var redirectUri = message.CreateAuthenticationRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
{
Logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
}
+ Logger.LogDebug(Resources.OIDCH_0037_RedirectUri, redirectUri);
+
Response.Redirect(redirectUri);
}
@@ -250,7 +274,7 @@ protected override async Task AuthenticateCoreAsync()
// assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small.
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
- && !string.IsNullOrWhiteSpace(Request.ContentType)
+ && !string.IsNullOrEmpty(Request.ContentType)
// May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
@@ -281,66 +305,74 @@ protected override async Task AuthenticateCoreAsync()
await Options.Notifications.MessageReceived(messageReceivedNotification);
if (messageReceivedNotification.HandledResponse)
{
- Logger.LogInformation(Resources.OIDCH_0002_MessageReceivedNotificationHandledResponse);
+ Logger.LogVerbose(Resources.OIDCH_0002_MessageReceivedNotificationHandledResponse);
return messageReceivedNotification.AuthenticationTicket;
}
if (messageReceivedNotification.Skipped)
{
- Logger.LogInformation(Resources.OIDCH_0003_MessageReceivedNotificationSkipped);
+ Logger.LogVerbose(Resources.OIDCH_0003_MessageReceivedNotificationSkipped);
return null;
}
- // runtime always adds state, if we don't find it OR we failed to 'unprotect' it this is not a message we should process.
- if (string.IsNullOrWhiteSpace(message.State))
+ var properties = new AuthenticationProperties();
+
+ // if state is missing, just log it
+ if (string.IsNullOrEmpty(message.State))
{
- Logger.LogError(Resources.OIDCH_0004_MessageStateIsNullOrWhiteSpace);
- return null;
+ Logger.LogWarning(Resources.OIDCH_0004_MessageStateIsNullOrEmpty);
}
-
- var properties = GetPropertiesFromState(message.State);
- if (properties == null)
+ else
{
- Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid);
- return null;
+ // if state exists and we failed to 'unprotect' this is not a message we should process.
+ properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State));
+ if (properties == null)
+ {
+ Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid);
+ return null;
+ }
+
+ string userstate = null;
+ properties.Items.TryGetValue(OpenIdConnectAuthenticationDefaults.UserstatePropertiesKey, out userstate);
+ message.State = userstate;
}
- // devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
- if (!string.IsNullOrWhiteSpace(message.Error))
+ // if any of the error fields are set, return null
+ if (!string.IsNullOrEmpty(message.Error))
{
- Logger.LogError(Resources.OIDCH_0006_MessageErrorNotNull, message.Error);
- throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageErrorNotNull, message.Error));
+ Logger.LogError(Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null");
+ throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"));
}
- AuthenticationTicket ticket = null;
- JwtSecurityToken jwt = null;
-
if (_configuration == null && Options.ConfigurationManager != null)
{
- Logger.LogDebug(Resources.OIDCH_0007_UpdatingConfiguration);
+ Logger.LogVerbose(Resources.OIDCH_0007_UpdatingConfiguration);
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
+ AuthenticationTicket ticket = null;
+ JwtSecurityToken jwt = null;
+
// OpenIdConnect protocol allows a Code to be received without the id_token
- if (!string.IsNullOrWhiteSpace(message.IdToken))
+ if (!string.IsNullOrEmpty(message.IdToken))
{
Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken);
var securityTokenReceivedNotification =
new SecurityTokenReceivedNotification(Context, Options)
{
- ProtocolMessage = message
+ ProtocolMessage = message,
};
await Options.Notifications.SecurityTokenReceived(securityTokenReceivedNotification);
if (securityTokenReceivedNotification.HandledResponse)
{
- Logger.LogInformation(Resources.OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse);
+ Logger.LogVerbose(Resources.OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse);
return securityTokenReceivedNotification.AuthenticationTicket;
}
if (securityTokenReceivedNotification.Skipped)
{
- Logger.LogInformation(Resources.OIDCH_0009_SecurityTokenReceivedNotificationSkipped);
+ Logger.LogVerbose(Resources.OIDCH_0009_SecurityTokenReceivedNotificationSkipped);
return null;
}
@@ -348,11 +380,11 @@ protected override async Task AuthenticateCoreAsync()
var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
- if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
+ if (string.IsNullOrEmpty(validationParameters.ValidIssuer))
{
validationParameters.ValidIssuer = _configuration.Issuer;
}
- else if (!string.IsNullOrWhiteSpace(_configuration.Issuer))
+ else if (!string.IsNullOrEmpty(_configuration.Issuer))
{
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(new[] { _configuration.Issuer }) ?? new[] { _configuration.Issuer };
}
@@ -371,7 +403,7 @@ protected override async Task AuthenticateCoreAsync()
if (jwt == null)
{
Logger.LogError(Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType());
- throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
+ throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0010_ValidatedSecurityTokenNotJwt, validatedToken?.GetType()));
}
}
}
@@ -379,16 +411,16 @@ protected override async Task AuthenticateCoreAsync()
if (validatedToken == null)
{
Logger.LogError(Resources.OIDCH_0011_UnableToValidateToken, message.IdToken);
- throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0011_UnableToValidateToken, message.IdToken));
+ throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0011_UnableToValidateToken, message.IdToken));
}
ticket = new AuthenticationTicket(principal, properties, Options.AuthenticationScheme);
- if (!string.IsNullOrWhiteSpace(message.SessionState))
+ if (!string.IsNullOrEmpty(message.SessionState))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.SessionState] = message.SessionState;
}
- if (_configuration != null && !string.IsNullOrWhiteSpace(_configuration.CheckSessionIframe))
+ if (_configuration != null && !string.IsNullOrEmpty(_configuration.CheckSessionIframe))
{
ticket.Properties.Items[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe;
}
@@ -419,13 +451,13 @@ protected override async Task AuthenticateCoreAsync()
await Options.Notifications.SecurityTokenValidated(securityTokenValidatedNotification);
if (securityTokenValidatedNotification.HandledResponse)
{
- Logger.LogInformation(Resources.OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse);
+ Logger.LogVerbose(Resources.OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse);
return securityTokenValidatedNotification.AuthenticationTicket;
}
if (securityTokenValidatedNotification.Skipped)
{
- Logger.LogInformation(Resources.OIDCH_0013_SecurityTokenValidatedNotificationSkipped);
+ Logger.LogVerbose(Resources.OIDCH_0013_SecurityTokenValidatedNotificationSkipped);
return null;
}
@@ -454,7 +486,7 @@ protected override async Task AuthenticateCoreAsync()
if (message.Code != null)
{
- Logger.LogDebug(Resources.OIDCH_0014_CodeReceived, message.Code);
+ Logger.LogDebug(Resources.OIDCH_0014_AuthorizationCodeReceived, message.Code);
if (ticket == null)
{
ticket = new AuthenticationTicket(properties, Options.AuthenticationScheme);
@@ -466,20 +498,20 @@ protected override async Task AuthenticateCoreAsync()
Code = message.Code,
JwtSecurityToken = jwt,
ProtocolMessage = message,
- RedirectUri = ticket.Properties.Items.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey) ?
- ticket.Properties.Items[OpenIdConnectAuthenticationDefaults.RedirectUriUsedForCodeKey] : string.Empty,
+ RedirectUri = ticket.Properties.Items.ContainsKey(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey) ?
+ ticket.Properties.Items[OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey] : string.Empty,
};
await Options.Notifications.AuthorizationCodeReceived(authorizationCodeReceivedNotification);
if (authorizationCodeReceivedNotification.HandledResponse)
{
- Logger.LogInformation(Resources.OIDCH_0015_CodeReceivedNotificationHandledResponse);
+ Logger.LogVerbose(Resources.OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse);
return authorizationCodeReceivedNotification.AuthenticationTicket;
}
if (authorizationCodeReceivedNotification.Skipped)
{
- Logger.LogInformation(Resources.OIDCH_0016_CodeReceivedNotificationSkipped);
+ Logger.LogVerbose(Resources.OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped);
return null;
}
}
@@ -493,7 +525,11 @@ protected override async Task AuthenticateCoreAsync()
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification.
if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException)))
{
- Options.ConfigurationManager.RequestRefresh();
+ if (Options.ConfigurationManager != null)
+ {
+ Logger.LogVerbose(Resources.OIDCH_0021_AutomaticConfigurationRefresh);
+ Options.ConfigurationManager.RequestRefresh();
+ }
}
var authenticationFailedNotification =
@@ -506,13 +542,13 @@ protected override async Task AuthenticateCoreAsync()
await Options.Notifications.AuthenticationFailed(authenticationFailedNotification);
if (authenticationFailedNotification.HandledResponse)
{
- Logger.LogInformation(Resources.OIDCH_0018_AuthenticationFailedNotificationHandledResponse);
+ Logger.LogVerbose(Resources.OIDCH_0018_AuthenticationFailedNotificationHandledResponse);
return authenticationFailedNotification.AuthenticationTicket;
}
if (authenticationFailedNotification.Skipped)
{
- Logger.LogInformation(Resources.OIDCH_0019_AuthenticationFailedNotificationSkipped);
+ Logger.LogVerbose(Resources.OIDCH_0019_AuthenticationFailedNotificationSkipped);
return null;
}
@@ -586,36 +622,6 @@ private string ReadNonceCookie(string nonce)
return null;
}
- private AuthenticationProperties GetPropertiesFromState(string state)
- {
- // assume a well formed query string: OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey=kasjd;fljasldkjflksdj<&c=d>
- var startIndex = 0;
- if (string.IsNullOrWhiteSpace(state) || (startIndex = state.IndexOf(OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey, StringComparison.Ordinal)) == -1)
- {
- return null;
- }
-
- var authenticationIndex = startIndex + OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey.Length;
- if (authenticationIndex == -1 || authenticationIndex == state.Length || state[authenticationIndex] != '=')
- {
- return null;
- }
-
- // scan rest of string looking for '&'
- authenticationIndex++;
- var endIndex = state.Substring(authenticationIndex, state.Length - authenticationIndex).IndexOf("&", StringComparison.Ordinal);
-
- // -1 => no other parameters are after the AuthenticationPropertiesKey
- if (endIndex == -1)
- {
- return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex).Replace('+', ' ')));
- }
- else
- {
- return Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(state.Substring(authenticationIndex, endIndex).Replace('+', ' ')));
- }
- }
-
///
/// Calls InvokeReplyPathAsync
///
@@ -636,7 +642,7 @@ private async Task InvokeReplyPathAsync()
}
// Redirect back to the original secured resource, if any.
- if (!string.IsNullOrWhiteSpace(ticket.Properties.RedirectUri))
+ if (!string.IsNullOrEmpty(ticket.Properties.RedirectUri))
{
Response.Redirect(ticket.Properties.RedirectUri);
return true;
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
index 9353f385c..a0dd2032e 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationMiddleware.cs
@@ -32,6 +32,8 @@ public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddlewareThe next middleware in the ASP.NET pipeline to invoke.
/// provider for creating a data protector.
/// factory for creating a .
+ /// for encoding query strings.
+ ///
/// a instance that will supply
/// if configureOptions is null.
/// a instance that will be passed to an instance of
@@ -39,11 +41,11 @@ public class OpenIdConnectAuthenticationMiddleware : AuthenticationMiddleware options,
[NotNull] IDataProtectionProvider dataProtectionProvider,
[NotNull] ILoggerFactory loggerFactory,
[NotNull] IUrlEncoder encoder,
[NotNull] IOptions externalOptions,
- [NotNull] IOptions options,
ConfigureOptions configureOptions = null)
: base(next, options, loggerFactory, encoder, configureOptions)
{
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationNotifications.cs
index 11c8c9ccc..9e795f886 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationNotifications.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectAuthenticationNotifications.cs
@@ -23,7 +23,6 @@ public OpenIdConnectAuthenticationNotifications()
MessageReceived = notification => Task.FromResult(0);
SecurityTokenReceived = notification => Task.FromResult(0);
SecurityTokenValidated = notification => Task.FromResult(0);
- RedirectToIdentityProvider = notification => Task.FromResult(0);
}
///
@@ -55,6 +54,5 @@ public OpenIdConnectAuthenticationNotifications()
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
///
public Func, Task> SecurityTokenValidated { get; set; }
-
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
index b95de8318..a4c392a8b 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.Designer.cs
@@ -93,7 +93,7 @@ internal static string OIDCH_0026_ApplyResponseChallengeAsync
}
///
- /// OIDCH_0027: converted 401 to 403.
+ /// OIDCH_0027: Converted 401 to 403.
///
internal static string OIDCH_0027_401_ConvertedTo_403
{
@@ -117,7 +117,7 @@ internal static string OIDCH_0029_ChallengeContextEqualsNull
}
///
- /// OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
+ /// OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
///
internal static string OIDCH_0030_Using_Properties_RedirectUri
{
@@ -125,7 +125,7 @@ internal static string OIDCH_0030_Using_Properties_RedirectUri
}
///
- /// OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.
+ /// OIDCH_0031: Using Options.RedirectUri for 'redirect_uri': '{0}'.
///
internal static string OIDCH_0031_Using_Options_RedirectUri
{
@@ -133,7 +133,7 @@ internal static string OIDCH_0031_Using_Options_RedirectUri
}
///
- /// OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.
+ /// OIDCH_0032: Using the CurrentUri for 'local redirect' post authentication: '{0}'.
///
internal static string OIDCH_0032_UsingCurrentUriRedirectUri
{
@@ -149,7 +149,7 @@ internal static string OIDCH_0033_TryAddNonceFailed
}
///
- /// OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse
+ /// OIDCH_0034: RedirectToIdentityProviderNotification.HandledResponse
///
internal static string OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse
{
@@ -157,7 +157,7 @@ internal static string OIDCH_0034_RedirectToIdentityProviderNotificationHandledR
}
///
- /// OIDCH_0035: redirectToIdentityProviderNotification.Skipped
+ /// OIDCH_0035: RedirectToIdentityProviderNotification.Skipped
///
internal static string OIDCH_0035_RedirectToIdentityProviderNotificationSkipped
{
@@ -165,13 +165,21 @@ internal static string OIDCH_0035_RedirectToIdentityProviderNotificationSkipped
}
///
- /// OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))
+ /// OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}'.)
///
internal static string OIDCH_0036_UriIsNotWellFormed
{
get { return ResourceManager.GetString("OIDCH_0036_UriIsNotWellFormed"); }
}
+ ///
+ /// OIDCH_0036: RedirectUri is: '{0}'.
+ ///
+ internal static string OIDCH_0037_RedirectUri
+ {
+ get { return ResourceManager.GetString("OIDCH_0037_RedirectUri"); }
+ }
+
///
/// OIDCH_0000: Entering: '{0}'.
///
@@ -197,7 +205,7 @@ internal static string FormatOIDCH_0001_MessageReceived(object p0)
}
///
- /// OIDCH_0002: messageReceivedNotification.HandledResponse
+ /// OIDCH_0002: MessageReceivedNotification.HandledResponse
///
internal static string OIDCH_0002_MessageReceivedNotificationHandledResponse
{
@@ -205,7 +213,7 @@ internal static string OIDCH_0002_MessageReceivedNotificationHandledResponse
}
///
- /// OIDCH_0003: messageReceivedNotification.Skipped
+ /// OIDCH_0003: MessageReceivedNotification.Skipped
///
internal static string OIDCH_0003_MessageReceivedNotificationSkipped
{
@@ -213,15 +221,15 @@ internal static string OIDCH_0003_MessageReceivedNotificationSkipped
}
///
- /// OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or whitespace. State is required to process the message.
+ /// OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or empty.
///
- internal static string OIDCH_0004_MessageStateIsNullOrWhiteSpace
+ internal static string OIDCH_0004_MessageStateIsNullOrEmpty
{
- get { return ResourceManager.GetString("OIDCH_0004_MessageStateIsNullOrWhiteSpace"); }
+ get { return ResourceManager.GetString("OIDCH_0004_MessageStateIsNullOrEmpty"); }
}
///
- /// OIDCH_0005: unable to unprotect the message.State
+ /// OIDCH_0005: Unable to unprotect the message.State
///
internal static string OIDCH_0005_MessageStateIsInvalid
{
@@ -229,15 +237,15 @@ internal static string OIDCH_0005_MessageStateIsInvalid
}
///
- /// OIDCH_0006_MessageErrorNotNull: '{0}'.
+ /// OIDCH_0006: Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.
///
- internal static string OIDCH_0006_MessageErrorNotNull
+ internal static string OIDCH_0006_MessageContainsError
{
- get { return ResourceManager.GetString("OIDCH_0006_MessageErrorNotNull"); }
+ get { return ResourceManager.GetString("OIDCH_0006_MessageContainsError"); }
}
///
- /// OIDCH_0007: updating configuration
+ /// OIDCH_0007: Updating configuration
///
internal static string OIDCH_0007_UpdatingConfiguration
{
@@ -245,7 +253,7 @@ internal static string OIDCH_0007_UpdatingConfiguration
}
///
- /// OIDCH_0008: securityTokenReceivedNotification.HandledResponse
+ /// OIDCH_0008: SecurityTokenReceivedNotification.HandledResponse
///
internal static string OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse
{
@@ -253,7 +261,7 @@ internal static string OIDCH_0008_SecurityTokenReceivedNotificationHandledRespon
}
///
- /// OIDCH_0009: securityTokenReceivedNotification.Skipped
+ /// OIDCH_0009: SecurityTokenReceivedNotification.Skipped
///
internal static string OIDCH_0009_SecurityTokenReceivedNotificationSkipped
{
@@ -269,7 +277,7 @@ internal static string OIDCH_0010_ValidatedSecurityTokenNotJwt
}
///
- /// OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: {0}."
+ /// OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: '{0}'.
///
internal static string OIDCH_0011_UnableToValidateToken
{
@@ -277,7 +285,7 @@ internal static string OIDCH_0011_UnableToValidateToken
}
///
- /// OIDCH_0012: securityTokenValidatedNotification.HandledResponse
+ /// OIDCH_0012: SecurityTokenValidatedNotification.HandledResponse
///
internal static string OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse
{
@@ -285,7 +293,7 @@ internal static string OIDCH_0012_SecurityTokenValidatedNotificationHandledRespo
}
///
- /// OIDCH_0013: securityTokenValidatedNotification.Skipped
+ /// OIDCH_0013: SecurityTokenValidatedNotification.Skipped
///
internal static string OIDCH_0013_SecurityTokenValidatedNotificationSkipped
{
@@ -293,31 +301,31 @@ internal static string OIDCH_0013_SecurityTokenValidatedNotificationSkipped
}
///
- /// OIDCH_0014: 'code' received: '{0}'
+ /// OIDCH_0014: AuthorizationCode received: '{0}'
///
- internal static string OIDCH_0014_CodeReceived
+ internal static string OIDCH_0014_AuthorizationCodeReceived
{
- get { return ResourceManager.GetString("OIDCH_0014_CodeReceived"); }
+ get { return ResourceManager.GetString("OIDCH_0014_AuthorizationCodeReceived"); }
}
///
- /// OIDCH_0015: codeReceivedNotification.HandledResponse")
+ /// OIDCH_0015: AuthorizationCodeReceivedNotification.HandledResponse
///
- internal static string OIDCH_0015_CodeReceivedNotificationHandledResponse
+ internal static string OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse
{
- get { return ResourceManager.GetString("OIDCH_0015_CodeReceivedNotificationHandledResponse"); }
+ get { return ResourceManager.GetString("OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse"); }
}
///
/// OIDCH_0016: codeReceivedNotification.Skipped
///
- internal static string OIDCH_0016_CodeReceivedNotificationSkipped
+ internal static string OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped
{
- get { return ResourceManager.GetString("OIDCH_0016_CodeReceivedNotificationSkipped"); }
+ get { return ResourceManager.GetString("OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped"); }
}
///
- /// OIDCH_0017: Exception occurred while processing message
+ /// OIDCH_0017: Exception occurred while processing message.
///
internal static string OIDCH_0017_ExceptionOccurredWhileProcessingMessage
{
@@ -341,11 +349,20 @@ internal static string OIDCH_0019_AuthenticationFailedNotificationSkipped
}
///
- /// OIDCH_0020: 'id_token' received: '{0}'
+ /// OIDCH_0020: 'id_token' received: '{0}'.
///
internal static string OIDCH_0020_IdTokenReceived
{
get { return ResourceManager.GetString("OIDCH_0020_IdTokenReceived"); }
}
+
+ ///
+ /// OIDCH_0021: exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.
+ ///
+ internal static string OIDCH_0021_AutomaticConfigurationRefresh
+ {
+ get { return ResourceManager.GetString("OIDCH_0021_AutomaticConfigurationRefresh"); }
+ }
+
}
}
diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
index 454dae209..f9e4b13bb 100644
--- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
+++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx
@@ -130,7 +130,7 @@
OIDCH_0026: Entering: '{0}'
- OIDCH_0027: converted 401 to 403.
+ OIDCH_0027: Converted 401 to 403.
OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.
@@ -139,10 +139,10 @@
OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication
- OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
+ OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
- OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.
+ OIDCH_0031: Using Options.RedirectUri for 'redirect_uri': '{0}'.
OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.
@@ -151,13 +151,16 @@
OIDCH_0033: ProtocolValidator.RequireNonce == true. Options.NonceCache.TryAddNonce returned false. This usually indicates the nonce is not unique or has been used. The nonce is: '{0}'.
- OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse
+ OIDCH_0034: RedirectToIdentityProviderNotification.HandledResponse
- OIDCH_0035: redirectToIdentityProviderNotification.Skipped
+ OIDCH_0035: RedirectToIdentityProviderNotification.Skipped
- OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))
+ OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: '{0}'.
+
+
+ OIDCH_0037: RedirectUri is: '{0}'.
OIDCH_0000: Entering: '{0}'.
@@ -166,60 +169,63 @@
OIDCH_0001: MessageReceived: '{0}'.
- OIDCH_0002: messageReceivedNotification.HandledResponse
+ OIDCH_0002: MessageReceivedNotification.HandledResponse
- OIDCH_0003: messageReceivedNotification.Skipped
+ OIDCH_0003: MessageReceivedNotification.Skipped
-
- OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or whitespace. State is required to process the message.
+
+ OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or empty.
-
- OIDCH_0005: unable to unprotect the message.State
+
+ OIDCH_0005: Unable to unprotect the message.State.
-
- OIDCH_0006_MessageErrorNotNull: '{0}'.
+
+ OIDCH_0006: Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.
- OIDCH_0007: updating configuration
+ OIDCH_0007: Updating configuration
- OIDCH_0008: securityTokenReceivedNotification.HandledResponse
+ OIDCH_0008: SecurityTokenReceivedNotification.HandledResponse
- OIDCH_0009: securityTokenReceivedNotification.Skipped
+ OIDCH_0009: SecurityTokenReceivedNotification.Skipped
OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'.
- OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: {0}."
+ OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: '{0}'."
- OIDCH_0012: securityTokenValidatedNotification.HandledResponse
+ OIDCH_0012: SecurityTokenValidatedNotification.HandledResponse
- OIDCH_0013: securityTokenValidatedNotification.Skipped
+ OIDCH_0013: SecurityTokenValidatedNotification.Skipped
-
- OIDCH_0014: 'code' received: '{0}'
+
+ OIDCH_0014: AuthorizationCode received: '{0}'.
-
- OIDCH_0015: codeReceivedNotification.HandledResponse
+
+ OIDCH_0015: AuthorizationCodeReceivedNotification.HandledResponse
-
- OIDCH_0016: codeReceivedNotification.Skipped
+
+ OIDCH_0016: AuthorizationCodeReceivedNotification.Skipped
-
- OIDCH_0017: Exception occurred while processing message
+
+ OIDCH_0017: Exception occurred while processing message.
- OIDCH_0018: authenticationFailedNotification.HandledResponse
+ OIDCH_0018: AuthenticationFailedNotification.HandledResponse
- OIDCH_0019: authenticationFailedNotification.Skipped
+ OIDCH_0019: AuthenticationFailedNotification.Skipped
OIDCH_0020: 'id_token' received: '{0}'
+
+ OIDCH_0021: exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.
+
diff --git a/src/Microsoft.AspNet.Authentication/Notifications/RedirectToIdentityProviderNotification.cs b/src/Microsoft.AspNet.Authentication/Notifications/RedirectToIdentityProviderNotification.cs
index a4b2d979e..775e65a03 100644
--- a/src/Microsoft.AspNet.Authentication/Notifications/RedirectToIdentityProviderNotification.cs
+++ b/src/Microsoft.AspNet.Authentication/Notifications/RedirectToIdentityProviderNotification.cs
@@ -1,16 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using System;
using Microsoft.AspNet.Http;
+using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Authentication.Notifications
{
+ ///
+ /// When a user configures the to be notified prior to redirecting to an IdentityProvider
+ /// an instance of is passed to the 'RedirectToIdentityProviderNotification".
+ ///
+ /// protocol specific message.
+ /// protocol specific options.
public class RedirectToIdentityProviderNotification : BaseNotification
{
- public RedirectToIdentityProviderNotification(HttpContext context, TOptions options) : base(context, options)
+ TMessage _message;
+
+ public RedirectToIdentityProviderNotification([NotNull] HttpContext context, [NotNull] TOptions options, [NotNull] TMessage protocolMessage ) : base(context, options)
{
+ ProtocolMessage = protocolMessage;
}
- public TMessage ProtocolMessage { get; set; }
+ ///
+ /// Gets or sets the .
+ ///
+ /// if 'value' is null.
+ public TMessage ProtocolMessage
+ {
+ get { return _message; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException("value");
+ }
+
+ _message = value;
+ }
+ }
}
}
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs
new file mode 100644
index 000000000..683f03563
--- /dev/null
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+using Microsoft.AspNet.Http.Authentication;
+using Microsoft.Framework.WebEncoders;
+
+namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
+{
+ ///
+ /// This formatter creates an easy to read string of the format: "'key1' 'value1' ..."
+ ///
+ public class AuthenticationPropertiesFormaterKeyValue : ISecureDataFormat
+ {
+ string _protectedString = Guid.NewGuid().ToString();
+
+ public string Protect(AuthenticationProperties data)
+ {
+ if (data == null || data.Items.Count == 0)
+ {
+ return "null";
+ }
+
+ var encoder = UrlEncoder.Default;
+ var sb = new StringBuilder();
+ foreach(var item in data.Items)
+ {
+ sb.Append(encoder.UrlEncode(item.Key) + " " + encoder.UrlEncode(item.Value) + " ");
+ }
+
+ return sb.ToString();
+ }
+
+ AuthenticationProperties ISecureDataFormat.Unprotect(string protectedText)
+ {
+ if (string.IsNullOrWhiteSpace(protectedText))
+ {
+ return null;
+ }
+
+ if (protectedText == "null")
+ {
+ return new AuthenticationProperties();
+ }
+
+ string[] items = protectedText.Split(' ');
+ if (items.Length % 2 != 0)
+ {
+ return null;
+ }
+
+ var propeties = new AuthenticationProperties();
+ for (int i = 0; i < items.Length - 1; i+=2)
+ {
+ propeties.Items.Add(items[i], items[i + 1]);
+ }
+
+ return propeties;
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/ExpectedQueryValues.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/ExpectedQueryValues.cs
new file mode 100644
index 000000000..ddf7a0e7e
--- /dev/null
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/ExpectedQueryValues.cs
@@ -0,0 +1,175 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.Framework.WebEncoders;
+using Microsoft.IdentityModel.Protocols;
+using Xunit;
+
+namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
+{
+ ///
+ /// This helper class is used to check that query string parameters are as expected.
+ ///
+ public class ExpectedQueryValues
+ {
+ public ExpectedQueryValues(string authority, OpenIdConnectConfiguration configuration = null)
+ {
+ Authority = authority;
+ Configuration = configuration ?? TestUtilities.DefaultOpenIdConnectConfiguration;
+ }
+
+ public static ExpectedQueryValues Defaults(string authority)
+ {
+ var result = new ExpectedQueryValues(authority);
+ result.Scope = OpenIdConnectScopes.OpenIdProfile;
+ result.ResponseType = OpenIdConnectResponseTypes.CodeIdToken;
+ return result;
+ }
+
+ public void CheckValues(string query, IEnumerable parameters)
+ {
+ var errors = new List();
+ if (!query.StartsWith(ExpectedAuthority))
+ {
+ errors.Add("ExpectedAuthority: " + ExpectedAuthority);
+ }
+
+ foreach(var str in parameters)
+ {
+ if (str == OpenIdConnectParameterNames.ClientId)
+ {
+ if (!query.Contains(ExpectedClientId))
+ errors.Add("ExpectedClientId: " + ExpectedClientId);
+
+ continue;
+ }
+
+ if (str == OpenIdConnectParameterNames.RedirectUri)
+ {
+ if(!query.Contains(ExpectedRedirectUri))
+ errors.Add("ExpectedRedirectUri: " + ExpectedRedirectUri);
+
+ continue;
+ }
+
+ if (str == OpenIdConnectParameterNames.Resource)
+ {
+ if(!query.Contains(ExpectedResource))
+ errors.Add("ExpectedResource: " + ExpectedResource);
+
+ continue;
+ }
+
+ if (str == OpenIdConnectParameterNames.ResponseMode)
+ {
+ if(!query.Contains(ExpectedResponseMode))
+ errors.Add("ExpectedResponseMode: " + ExpectedResponseMode);
+
+ continue;
+ }
+
+ if (str == OpenIdConnectParameterNames.Scope)
+ {
+ if (!query.Contains(ExpectedScope))
+ errors.Add("ExpectedScope: " + ExpectedScope);
+
+ continue;
+ }
+
+ if (str == OpenIdConnectParameterNames.State)
+ {
+ if (!query.Contains(ExpectedState))
+ errors.Add("ExpectedState: " + ExpectedState);
+
+ continue;
+ }
+ }
+
+ if (errors.Count > 0)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("query string not as expected: " + Environment.NewLine + query + Environment.NewLine);
+ foreach (var str in errors)
+ {
+ sb.AppendLine(str);
+ }
+
+ Debug.WriteLine(sb.ToString());
+ Assert.True(false, sb.ToString());
+ }
+ }
+
+ public UrlEncoder Encoder { get; set; } = UrlEncoder.Default;
+
+ public string Authority { get; set; }
+
+ public string ClientId { get; set; } = Guid.NewGuid().ToString();
+
+ public string RedirectUri { get; set; } = Guid.NewGuid().ToString();
+
+ public OpenIdConnectRequestType RequestType { get; set; } = OpenIdConnectRequestType.AuthenticationRequest;
+
+ public string Resource { get; set; } = Guid.NewGuid().ToString();
+
+ public string ResponseMode { get; set; } = OpenIdConnectResponseModes.FormPost;
+
+ public string ResponseType { get; set; } = Guid.NewGuid().ToString();
+
+ public string Scope { get; set; } = Guid.NewGuid().ToString();
+
+ public string State { get; set; } = Guid.NewGuid().ToString();
+
+ public string ExpectedAuthority
+ {
+ get
+ {
+ if (RequestType == OpenIdConnectRequestType.TokenRequest)
+ {
+ return Configuration?.EndSessionEndpoint ?? Authority + @"/oauth2/token";
+ }
+ else if (RequestType == OpenIdConnectRequestType.LogoutRequest)
+ {
+ return Configuration?.TokenEndpoint ?? Authority + @"/oauth2/logout";
+ }
+
+ return Configuration?.AuthorizationEndpoint ?? Authority + (@"/oauth2/authorize");
+ }
+ }
+
+ public OpenIdConnectConfiguration Configuration { get; set; }
+
+ public string ExpectedClientId
+ {
+ get { return OpenIdConnectParameterNames.ClientId + "=" + Encoder.UrlEncode(ClientId); }
+ }
+
+ public string ExpectedRedirectUri
+ {
+ get { return OpenIdConnectParameterNames.RedirectUri + "=" + Encoder.UrlEncode(RedirectUri); }
+ }
+
+ public string ExpectedResource
+ {
+ get { return OpenIdConnectParameterNames.Resource + "=" + Encoder.UrlEncode(Resource); }
+ }
+
+ public string ExpectedResponseMode
+ {
+ get { return OpenIdConnectParameterNames.ResponseMode + "=" + Encoder.UrlEncode(ResponseMode); }
+ }
+
+ public string ExpectedScope
+ {
+ get { return OpenIdConnectParameterNames.Scope + "=" + Encoder.UrlEncode(Scope); }
+ }
+
+ public string ExpectedState
+ {
+ get { return OpenIdConnectParameterNames.State + "=" + Encoder.UrlEncode(State); }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/InMemoryLogger.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/InMemoryLogger.cs
new file mode 100644
index 000000000..67a2668c6
--- /dev/null
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/InMemoryLogger.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.Framework.Logging;
+
+namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
+{
+ public class InMemoryLogger : ILogger, IDisposable
+ {
+ LogLevel _logLevel = 0;
+
+ public InMemoryLogger(LogLevel logLevel = LogLevel.Debug)
+ {
+ _logLevel = logLevel;
+ }
+
+ List _logEntries = new List();
+
+ public IDisposable BeginScopeImpl(object state)
+ {
+ return this;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ return (logLevel >= _logLevel);
+ }
+
+ public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func
public class OpenIdConnectHandlerTests
{
- static List CompleteLogEntries;
- static Dictionary LogEntries;
-
- static OpenIdConnectHandlerTests()
- {
- LogEntries =
- new Dictionary()
- {
- { "OIDCH_0000:", LogLevel.Debug },
- { "OIDCH_0001:", LogLevel.Debug },
- { "OIDCH_0002:", LogLevel.Information },
- { "OIDCH_0003:", LogLevel.Information },
- { "OIDCH_0004:", LogLevel.Error },
- { "OIDCH_0005:", LogLevel.Error },
- { "OIDCH_0006:", LogLevel.Error },
- { "OIDCH_0007:", LogLevel.Error },
- { "OIDCH_0008:", LogLevel.Debug },
- { "OIDCH_0009:", LogLevel.Debug },
- { "OIDCH_0010:", LogLevel.Error },
- { "OIDCH_0011:", LogLevel.Error },
- { "OIDCH_0012:", LogLevel.Debug },
- { "OIDCH_0013:", LogLevel.Debug },
- { "OIDCH_0014:", LogLevel.Debug },
- { "OIDCH_0015:", LogLevel.Debug },
- { "OIDCH_0016:", LogLevel.Debug },
- { "OIDCH_0017:", LogLevel.Error },
- { "OIDCH_0018:", LogLevel.Debug },
- { "OIDCH_0019:", LogLevel.Debug },
- { "OIDCH_0020:", LogLevel.Debug },
- { "OIDCH_0026:", LogLevel.Error },
- };
-
- BuildLogEntryList();
- }
-
- ///
- /// Builds the complete list of log entries that are available in the runtime.
- ///
- private static void BuildLogEntryList()
- {
- CompleteLogEntries = new List();
- foreach (var entry in LogEntries)
- {
- CompleteLogEntries.Add(new LogEntry { State = entry.Key, Level = entry.Value });
- }
- }
+ private const string nonceForJwt = "abc";
+ private static SecurityToken specCompliantJwt = new JwtSecurityToken("issuer", "audience", new List { new Claim("iat", EpochTime.GetIntDate(DateTime.UtcNow).ToString()), new Claim("nonce", nonceForJwt) }, DateTime.UtcNow, DateTime.UtcNow + TimeSpan.FromDays(1));
+ private const string ExpectedStateParameter = "expectedState";
///
/// Sanity check that logging is filtering, hi / low water marks are checked
@@ -85,7 +40,7 @@ private static void BuildLogEntryList()
[Fact]
public void LoggingLevel()
{
- var logger = new CustomLogger(LogLevel.Debug);
+ var logger = new InMemoryLogger(LogLevel.Debug);
logger.IsEnabled(LogLevel.Critical).ShouldBe(true);
logger.IsEnabled(LogLevel.Debug).ShouldBe(true);
logger.IsEnabled(LogLevel.Error).ShouldBe(true);
@@ -93,7 +48,7 @@ public void LoggingLevel()
logger.IsEnabled(LogLevel.Verbose).ShouldBe(true);
logger.IsEnabled(LogLevel.Warning).ShouldBe(true);
- logger = new CustomLogger(LogLevel.Critical);
+ logger = new InMemoryLogger(LogLevel.Critical);
logger.IsEnabled(LogLevel.Critical).ShouldBe(true);
logger.IsEnabled(LogLevel.Debug).ShouldBe(false);
logger.IsEnabled(LogLevel.Error).ShouldBe(false);
@@ -102,214 +57,223 @@ public void LoggingLevel()
logger.IsEnabled(LogLevel.Warning).ShouldBe(false);
}
- ///
- /// Test produces expected logs.
- /// Each call to 'RunVariation' is configured with an and .
- /// The list of expected log entries is checked and any errors reported.
- /// captures the logs so they can be prepared.
- ///
- ///
- [Fact]
- public async Task AuthenticateCore()
+ [Theory, MemberData("AuthenticateCoreStateDataSet")]
+ public async Task AuthenticateCoreState(Action action, OpenIdConnectMessage message)
{
- //System.Diagnostics.Debugger.Launch();
-
- var propertiesFormatter = new AuthenticationPropertiesFormater();
- var protectedProperties = propertiesFormatter.Protect(new AuthenticationProperties());
- var state = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + UrlEncoder.Default.UrlEncode(protectedProperties);
- var code = Guid.NewGuid().ToString();
- var message =
- new OpenIdConnectMessage
- {
- Code = code,
- State = state,
- };
-
- var errors = new Dictionary>>();
-
- var logsEntriesExpected = new int[] { 0, 1, 7, 14, 15 };
- await RunVariation(LogLevel.Debug, message, CodeReceivedHandledOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] { 0, 1, 7, 14, 16 };
- await RunVariation(LogLevel.Debug, message, CodeReceivedSkippedOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] { 0, 1, 7, 14 };
- await RunVariation(LogLevel.Debug, message, DefaultOptions, errors, logsEntriesExpected);
-
- // each message below should return before processing the idtoken
- message.IdToken = "invalid_token";
-
- logsEntriesExpected = new int[] { 0, 1, 2 };
- await RunVariation(LogLevel.Debug, message, MessageReceivedHandledOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[]{ 2 };
- await RunVariation(LogLevel.Information, message, MessageReceivedHandledOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] { 0, 1, 3 };
- await RunVariation(LogLevel.Debug, message, MessageReceivedSkippedOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] { 3 };
- await RunVariation(LogLevel.Information, message, MessageReceivedSkippedOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] {0, 1, 7, 20, 8 };
- await RunVariation(LogLevel.Debug, message, SecurityTokenReceivedHandledOptions, errors, logsEntriesExpected);
-
- logsEntriesExpected = new int[] {0, 1, 7, 20, 9 };
- await RunVariation(LogLevel.Debug, message, SecurityTokenReceivedSkippedOptions, errors, logsEntriesExpected);
-
-#if _Verbose
- Console.WriteLine("\n ===== \n");
- DisplayErrors(errors);
-#endif
- errors.Count.ShouldBe(0);
+ var handler = new OpenIdConnectAuthenticationHandlerForTestingAuthenticate(EmptyTask, EmptyTask);
+ var server = CreateServer(new ConfigureOptions(action), UrlEncoder.Default, handler);
+ await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters.Where(pair => pair.Value != null)));
}
- ///
- /// Tests that processes a messaage as expected.
- /// The test runs two independant paths: Using and
- ///
- /// for this variation
- /// the that has arrived
- /// the delegate used for setting the options.
- /// container for propogation of errors.
- /// the expected log entries
- /// a Task
- private async Task RunVariation(LogLevel logLevel, OpenIdConnectMessage message, Action action, Dictionary>> errors, int[] logsEntriesExpected)
+ public static TheoryData, OpenIdConnectMessage> AuthenticateCoreStateDataSet
{
- var expectedLogs = PopulateLogEntries(logsEntriesExpected);
- string variation = action.Method.ToString().Substring(5, action.Method.ToString().IndexOf('(') - 5);
-#if _Verbose
- Console.WriteLine(Environment.NewLine + "=====" + Environment.NewLine + "Variation: " + variation + ", LogLevel: " + logLevel.ToString() + Environment.NewLine + Environment.NewLine + "Expected Logs: ");
- DisplayLogs(expectedLogs);
- Console.WriteLine(Environment.NewLine + "Logs using ConfigureOptions:");
-#endif
- var form = new FormUrlEncodedContent(message.Parameters);
- var loggerFactory = new CustomLoggerFactory(logLevel);
- var server = CreateServer(new CustomConfigureOptions(action), loggerFactory);
- await server.CreateClient().PostAsync("http://localhost", form);
- CheckLogs(variation + ":ConfigOptions", loggerFactory.Logger.Logs, expectedLogs, errors);
-
-#if _Verbose
- Console.WriteLine(Environment.NewLine + "Logs using IOptions:");
-#endif
- form = new FormUrlEncodedContent(message.Parameters);
- loggerFactory = new CustomLoggerFactory(logLevel);
- server = CreateServer(new Options(action), loggerFactory);
- await server.CreateClient().PostAsync("http://localhost", form);
- CheckLogs(variation + ":IOptions", loggerFactory.Logger.Logs, expectedLogs, errors);
+ get
+ {
+ var formater = new AuthenticationPropertiesFormaterKeyValue();
+ var properties = new AuthenticationProperties();
+ var dataset = new TheoryData, OpenIdConnectMessage>();
+
+ // expected user state is added to the message.Parameters.Items[ExpectedStateParameter]
+ // Userstate == null
+ var message = new OpenIdConnectMessage();
+ message.State = UrlEncoder.Default.UrlEncode(formater.Protect(properties));
+ message.Code = Guid.NewGuid().ToString();
+ message.Parameters.Add(ExpectedStateParameter, null);
+ dataset.Add(SetStateOptions, message);
+
+ // Userstate != null
+ message = new OpenIdConnectMessage();
+ properties.Items.Clear();
+ var userstate = Guid.NewGuid().ToString();
+ message.Code = Guid.NewGuid().ToString();
+ properties.Items.Add(OpenIdConnectAuthenticationDefaults.UserstatePropertiesKey, userstate);
+ message.State = UrlEncoder.Default.UrlEncode(formater.Protect(properties));
+ message.Parameters.Add(ExpectedStateParameter, userstate);
+ dataset.Add(SetStateOptions, message);
+ return dataset;
+ }
}
- ///
- /// Populates a list of expected log entries for a test variation.
- ///
- /// the index for the in CompleteLogEntries of interest.
- /// a that represents the expected entries for a test variation.
- private List PopulateLogEntries(int[] items)
+ // Setup a notification to check for expected state.
+ // The state gets set by the runtime after the 'MessageReceivedNotification'
+ private static void SetStateOptions(OpenIdConnectAuthenticationOptions options)
{
- var entries = new List();
- foreach(var item in items)
+ options.AuthenticationScheme = "OpenIdConnectHandlerTest";
+ options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
+ options.ClientId = Guid.NewGuid().ToString();
+ options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
+ options.Notifications = new OpenIdConnectAuthenticationNotifications
{
- entries.Add(CompleteLogEntries[item]);
- }
+ AuthorizationCodeReceived = notification =>
+ {
+ if (notification.ProtocolMessage.State == null && !notification.ProtocolMessage.Parameters.ContainsKey(ExpectedStateParameter))
+ return Task.FromResult(null);
- return entries;
+ if (notification.ProtocolMessage.State == null || !notification.ProtocolMessage.Parameters.ContainsKey(ExpectedStateParameter))
+ Assert.True(false, "(notification.ProtocolMessage.State=!= null || !notification.ProtocolMessage.Parameters.ContainsKey(expectedState)");
+
+ Assert.Equal(notification.ProtocolMessage.State, notification.ProtocolMessage.Parameters[ExpectedStateParameter]);
+ return Task.FromResult(null);
+ }
+ };
}
- private void DisplayLogs(List logs)
+ [Theory, MemberData("AuthenticateCoreDataSet")]
+ public async Task AuthenticateCore(LogLevel logLevel, int[] expectedLogIndexes, Action action, OpenIdConnectMessage message)
{
- foreach (var logentry in logs)
- {
- Console.WriteLine(logentry.ToString());
- }
+ var errors = new List>();
+ var expectedLogs = LoggingUtilities.PopulateLogEntries(expectedLogIndexes);
+ var handler = new OpenIdConnectAuthenticationHandlerForTestingAuthenticate(EmptyTask, EmptyTask);
+ var loggerFactory = new InMemoryLoggerFactory(logLevel);
+ var server = CreateServer(new ConfigureOptions(action), UrlEncoder.Default, loggerFactory, handler);
+
+ await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters));
+ LoggingUtilities.CheckLogs(loggerFactory.Logger.Logs, expectedLogs, errors);
+ Debug.WriteLine(LoggingUtilities.LoggingErrors(errors));
+ Assert.True(errors.Count == 0, LoggingUtilities.LoggingErrors(errors));
}
- private void DisplayErrors(Dictionary>> errors)
+ public static TheoryData, OpenIdConnectMessage> AuthenticateCoreDataSet
{
- if (errors.Count > 0)
+ get
{
- foreach (var error in errors)
- {
- Console.WriteLine("Error in Variation: " + error.Key);
- foreach (var logError in error.Value)
- {
- Console.WriteLine("*Captured*, *Expected* : *" + (logError.Item1?.ToString() ?? "null") + "*, *" + (logError.Item2?.ToString() ?? "null") + "*");
- }
- Console.WriteLine(Environment.NewLine);
- }
+ var formater = new AuthenticationPropertiesFormaterKeyValue();
+ var dataset = new TheoryData, OpenIdConnectMessage>();
+ var properties = new AuthenticationProperties();
+ var message = new OpenIdConnectMessage();
+ var validState = UrlEncoder.Default.UrlEncode(formater.Protect(properties));
+ message.State = validState;
+
+ // MessageReceived - Handled / Skipped
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 2 }, MessageReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 2 }, MessageReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, MessageReceivedHandledOptions, message);
+
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 3 }, MessageReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 3 }, MessageReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, MessageReceivedSkippedOptions, message);
+
+ // State - null, empty string, invalid
+ message = new OpenIdConnectMessage();
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7 }, StateNullOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateNullOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, StateNullOptions, message);
+
+ message = new OpenIdConnectMessage();
+ message.State = string.Empty;
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 7 }, StateEmptyOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 4, 7 }, StateEmptyOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, StateEmptyOptions, message);
+
+ message = new OpenIdConnectMessage();
+ message.State = Guid.NewGuid().ToString();
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 5 }, StateInvalidOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 5 }, StateInvalidOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { 5 }, StateInvalidOptions, message);
+
+ // OpenIdConnectMessage.Error != null
+ message = new OpenIdConnectMessage();
+ message.Error = "Error";
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 4, 6, 17, 18 }, MessageWithErrorOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 4, 6, 17, 18 }, MessageWithErrorOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { 6, 17 }, MessageWithErrorOptions, message);
+
+ // SecurityTokenReceived - Handled / Skipped
+ message = new OpenIdConnectMessage();
+ message.IdToken = "invalid";
+ message.State = validState;
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 8 }, SecurityTokenReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 8 }, SecurityTokenReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenReceivedHandledOptions, message);
+
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 9 }, SecurityTokenReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 9 }, SecurityTokenReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenReceivedSkippedOptions, message);
+
+ // SecurityTokenValidation - ReturnsNull, Throws, Validates
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 11, 17, 18 }, SecurityTokenValidatorCannotReadToken, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 11, 17, 18 }, SecurityTokenValidatorCannotReadToken, message);
+ dataset.Add(LogLevel.Error, new int[] { 11, 17 }, SecurityTokenValidatorCannotReadToken, message);
+
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 17, 21, 18 }, SecurityTokenValidatorThrows, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 17, 21, 18 }, SecurityTokenValidatorThrows, message);
+ dataset.Add(LogLevel.Error, new int[] { 17 }, SecurityTokenValidatorThrows, message);
+
+ message.Nonce = nonceForJwt;
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20 }, SecurityTokenValidatorValidatesAllTokens, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7 }, SecurityTokenValidatorValidatesAllTokens, message);
+ dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatorValidatesAllTokens, message);
+
+ // SecurityTokenValidation - Handled / Skipped
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 12 }, SecurityTokenValidatedHandledOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 12 }, SecurityTokenValidatedHandledOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatedHandledOptions, message);
+
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 20, 13 }, SecurityTokenValidatedSkippedOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 13 }, SecurityTokenValidatedSkippedOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, SecurityTokenValidatedSkippedOptions, message);
+
+ // AuthenticationCodeReceived - Handled / Skipped
+ message = new OpenIdConnectMessage();
+ message.Code = Guid.NewGuid().ToString();
+ message.State = validState;
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 14, 15 }, AuthorizationCodeReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 15 }, AuthorizationCodeReceivedHandledOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedHandledOptions, message);
+
+ dataset.Add(LogLevel.Debug, new int[] { 0, 1, 7, 14, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Verbose, new int[] { 7, 16 }, AuthorizationCodeReceivedSkippedOptions, message);
+ dataset.Add(LogLevel.Error, new int[] { }, AuthorizationCodeReceivedSkippedOptions, message);
+
+ return dataset;
}
}
- ///
- /// Adds to errors if a variation if any are found.
- ///
- /// if this has been seen before, errors will be appended, test results are easier to understand if this is unique.
- /// these are the logs the runtime generated
- /// these are the errors that were expected
- /// the dictionary to record any errors
- private void CheckLogs(string variation, List capturedLogs, List expectedLogs, Dictionary>> errors)
+#region Configure Options for AuthenticateCore variations
+
+ private static void DefaultOptions(OpenIdConnectAuthenticationOptions options)
{
- var localErrors = new List>();
+ options.AuthenticationScheme = "OpenIdConnectHandlerTest";
+ options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
+ options.ClientId = Guid.NewGuid().ToString();
+ options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
+ }
- if (capturedLogs.Count >= expectedLogs.Count)
- {
- for (int i = 0; i < capturedLogs.Count; i++)
- {
- if (i + 1 > expectedLogs.Count)
- {
- localErrors.Add(new Tuple(capturedLogs[i], null));
- }
- else
- {
- if (!TestUtilities.AreEqual(capturedLogs[i], expectedLogs[i]))
- {
- localErrors.Add(new Tuple(capturedLogs[i], expectedLogs[i]));
- }
- }
- }
- }
- else
- {
- for (int i = 0; i < expectedLogs.Count; i++)
+ private static void AuthorizationCodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ options.Notifications =
+ new OpenIdConnectAuthenticationNotifications
{
- if (i + 1 > capturedLogs.Count)
- {
- localErrors.Add(new Tuple(null, expectedLogs[i]));
- }
- else
+ AuthorizationCodeReceived = (notification) =>
{
- if (!TestUtilities.AreEqual(expectedLogs[i], capturedLogs[i]))
- {
- localErrors.Add(new Tuple(capturedLogs[i], expectedLogs[i]));
- }
+ notification.HandleResponse();
+ return Task.FromResult(null);
}
- }
- }
+ };
+ }
- if (localErrors.Count != 0)
- {
- if (errors.ContainsKey(variation))
+ private static void AuthorizationCodeReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ options.Notifications =
+ new OpenIdConnectAuthenticationNotifications
{
- foreach (var error in localErrors)
+ AuthorizationCodeReceived = (notification) =>
{
- errors[variation].Add(error);
+ notification.SkipToNextMiddleware();
+ return Task.FromResult(null);
}
- }
- else
- {
- errors[variation] = localErrors;
- }
- }
+ };
}
- #region Configure Options
-
- private static void CodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
+ private static void AuthenticationErrorHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
- AuthorizationCodeReceived = (notification) =>
+ AuthenticationFailed = (notification) =>
{
notification.HandleResponse();
return Task.FromResult(null);
@@ -317,13 +281,13 @@ private static void CodeReceivedHandledOptions(OpenIdConnectAuthenticationOption
};
}
- private static void CodeReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
+ private static void AuthenticationErrorSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
- AuthorizationCodeReceived = (notification) =>
+ AuthenticationFailed = (notification) =>
{
notification.SkipToNextMiddleware();
return Task.FromResult(null);
@@ -331,13 +295,6 @@ private static void CodeReceivedSkippedOptions(OpenIdConnectAuthenticationOption
};
}
- private static void DefaultOptions(OpenIdConnectAuthenticationOptions options)
- {
- options.AuthenticationScheme = "OpenIdConnectHandlerTest";
- options.ConfigurationManager = ConfigurationManager.DefaultStaticConfigurationManager;
- options.StateDataFormat = new AuthenticationPropertiesFormater();
- }
-
private static void MessageReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
@@ -366,6 +323,11 @@ private static void MessageReceivedSkippedOptions(OpenIdConnectAuthenticationOpt
};
}
+ private static void MessageWithErrorOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ AuthenticationErrorHandledOptions(options);
+ }
+
private static void SecurityTokenReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
@@ -394,9 +356,42 @@ private static void SecurityTokenReceivedSkippedOptions(OpenIdConnectAuthenticat
};
}
- private static void SecurityTokenValidatedHandledOptions(OpenIdConnectAuthenticationOptions options)
+ private static void SecurityTokenValidatorCannotReadToken(OpenIdConnectAuthenticationOptions options)
+ {
+ AuthenticationErrorHandledOptions(options);
+ var mockValidator = new Mock();
+ SecurityToken jwt = null;
+ mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out jwt)).Returns(new ClaimsPrincipal());
+ mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(false);
+ options.SecurityTokenValidators = new Collection { mockValidator.Object };
+ }
+
+ private static void SecurityTokenValidatorThrows(OpenIdConnectAuthenticationOptions options)
+ {
+ AuthenticationErrorHandledOptions(options);
+ var mockValidator = new Mock();
+ SecurityToken jwt = null;
+ mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out jwt)).Throws();
+ mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(true);
+ options.SecurityTokenValidators = new Collection { mockValidator.Object };
+ }
+
+ private static void SecurityTokenValidatorValidatesAllTokens(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
+ var mockValidator = new Mock();
+ mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out specCompliantJwt)).Returns(new ClaimsPrincipal());
+ mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(true);
+ var mockNonceCache = new Mock();
+ mockNonceCache.Setup(n => n.TryRemoveNonce(It.IsAny())).Returns(true);
+ options.SecurityTokenValidators = new Collection { mockValidator.Object };
+ options.NonceCache = mockNonceCache.Object;
+ options.ProtocolValidator.RequireTimeStampInNonce = false;
+ }
+
+ private static void SecurityTokenValidatedHandledOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ SecurityTokenValidatorValidatesAllTokens(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@@ -410,7 +405,7 @@ private static void SecurityTokenValidatedHandledOptions(OpenIdConnectAuthentica
private static void SecurityTokenValidatedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
- DefaultOptions(options);
+ SecurityTokenValidatorValidatesAllTokens(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@@ -422,14 +417,31 @@ private static void SecurityTokenValidatedSkippedOptions(OpenIdConnectAuthentica
};
}
- #endregion
+ private static void StateNullOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ }
+
+ private static void StateEmptyOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ }
+
+ private static void StateInvalidOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ DefaultOptions(options);
+ }
+
+#endregion
+
+ private static Task EmptyTask() { return Task.FromResult(0); }
- private static TestServer CreateServer(IOptions options, ILoggerFactory loggerFactory)
+ private static TestServer CreateServer(ConfigureOptions options, IUrlEncoder encoder, OpenIdConnectAuthenticationHandler handler = null)
{
return TestServer.Create(
app =>
{
- app.UseCustomOpenIdConnectAuthentication(options, loggerFactory);
+ app.UseMiddleware(options, encoder, handler);
app.Use(async (context, next) =>
{
await next();
@@ -443,12 +455,12 @@ private static TestServer CreateServer(IOptions configureOptions, IUrlEncoder encoder, ILoggerFactory loggerFactory, OpenIdConnectAuthenticationHandler handler = null)
{
return TestServer.Create(
app =>
{
- app.UseCustomOpenIdConnectAuthentication(configureOptions, loggerFactory);
+ app.UseMiddleware(configureOptions, encoder, loggerFactory, handler);
app.Use(async (context, next) =>
{
await next();
@@ -462,278 +474,4 @@ private static TestServer CreateServer(CustomConfigureOptions configureOptions,
);
}
}
-
- ///
- /// Extension specifies as the middleware.
- ///
- public static class OpenIdConnectAuthenticationExtensions
- {
- ///
- /// Adds the into the ASP.NET runtime.
- ///
- /// The application builder
- /// Options which control the processing of the OpenIdConnect protocol and token validation.
- /// custom loggerFactory
- /// The application builder
- public static IApplicationBuilder UseCustomOpenIdConnectAuthentication(this IApplicationBuilder app, CustomConfigureOptions customConfigureOption, ILoggerFactory loggerFactory)
- {
- return app.UseMiddleware(customConfigureOption, loggerFactory);
- }
-
- ///
- /// Adds the into the ASP.NET runtime.
- ///
- /// The application builder
- /// Options which control the processing of the OpenIdConnect protocol and token validation.
- /// custom loggerFactory
- /// The application builder
- public static IApplicationBuilder UseCustomOpenIdConnectAuthentication(this IApplicationBuilder app, IOptions options, ILoggerFactory loggerFactory)
- {
- return app.UseMiddleware(options, loggerFactory);
- }
- }
-
- ///
- /// Provides a Facade over IOptions
- ///
- public class Options : IOptions
- {
- OpenIdConnectAuthenticationOptions _options;
-
- public Options(Action action)
- {
- _options = new OpenIdConnectAuthenticationOptions();
- action(_options);
- }
-
- OpenIdConnectAuthenticationOptions IOptions.Options
- {
- get
- {
- return _options;
- }
- }
-
- ///
- /// For now returns _options
- ///
- /// configuration to return
- ///
- public OpenIdConnectAuthenticationOptions GetNamedOptions(string name)
- {
- return _options;
- }
- }
-
- public class CustomConfigureOptions : ConfigureOptions
- {
- public CustomConfigureOptions(Action action)
- : base(action)
- {
- }
-
- public override void Configure(OpenIdConnectAuthenticationOptions options, string name = "")
- {
- base.Configure(options, name);
- return;
- }
- }
-
- ///
- /// Used to control which methods are handled
- ///
- public class CustomOpenIdConnectAuthenticationHandler : OpenIdConnectAuthenticationHandler
- {
- public async Task BaseInitializeAsyncPublic(AuthenticationOptions options, HttpContext context, ILogger logger, IUrlEncoder encoder)
- {
- await base.BaseInitializeAsync(options, context, logger, encoder);
- }
-
- public override bool ShouldHandleScheme(string authenticationScheme)
- {
- return true;
- }
-
- public override void Challenge(ChallengeContext context)
- {
- }
-
- protected override void ApplyResponseChallenge()
- {
- }
-
- protected override async Task ApplyResponseChallengeAsync()
- {
- var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification(Context, Options)
- {
- };
-
- await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
- }
- }
-
- ///
- /// Used to set as the AuthenticationHandler
- /// which can be configured to handle certain messages.
- ///
- public class CustomOpenIdConnectAuthenticationMiddleware : OpenIdConnectAuthenticationMiddleware
- {
- public CustomOpenIdConnectAuthenticationMiddleware(
- RequestDelegate next,
- IDataProtectionProvider dataProtectionProvider,
- ILoggerFactory loggerFactory,
- IUrlEncoder encoder,
- IOptions externalOptions,
- IOptions options,
- ConfigureOptions configureOptions = null
- )
- : base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions)
- {
- Logger = (loggerFactory as CustomLoggerFactory).Logger;
- }
-
- protected override AuthenticationHandler CreateHandler()
- {
- return new CustomOpenIdConnectAuthenticationHandler();
- }
- }
-
- public class LogEntry
- {
- public LogEntry() { }
-
- public int EventId { get; set; }
-
- public Exception Exception { get; set; }
-
- public Func Formatter { get; set; }
-
- public LogLevel Level { get; set; }
-
- public object State { get; set; }
-
- public override string ToString()
- {
- if (Formatter != null)
- {
- return Formatter(this.State, this.Exception);
- }
- else
- {
- string message = (Formatter != null ? Formatter(State, Exception) : (State?.ToString() ?? "null"));
- message += ", LogLevel: " + Level.ToString();
- message += ", EventId: " + EventId.ToString();
- message += ", Exception: " + (Exception == null ? "null" : Exception.Message);
- return message;
- }
- }
- }
-
- public class CustomLogger : ILogger, IDisposable
- {
- LogLevel _logLevel = 0;
-
- public CustomLogger(LogLevel logLevel = LogLevel.Debug)
- {
- _logLevel = logLevel;
- }
-
- List logEntries = new List();
-
- public IDisposable BeginScopeImpl(object state)
- {
- return this;
- }
-
- public void Dispose()
- {
- }
-
- public bool IsEnabled(LogLevel logLevel)
- {
- return (logLevel >= _logLevel);
- }
-
- public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func formatter)
- {
- if (IsEnabled(logLevel))
- {
- logEntries.Add(
- new LogEntry
- {
- EventId = eventId,
- Exception = exception,
- Formatter = formatter,
- Level = logLevel,
- State = state,
- });
-
-#if _Verbose
- Console.WriteLine(state?.ToString() ?? "state null");
-#endif
- }
- }
-
- public List Logs { get { return logEntries; } }
- }
-
- public class CustomLoggerFactory : ILoggerFactory
- {
- CustomLogger _logger;
- LogLevel _logLevel = LogLevel.Debug;
-
- public CustomLoggerFactory(LogLevel logLevel)
- {
- _logLevel = logLevel;
- _logger = new CustomLogger(_logLevel);
- }
-
- public LogLevel MinimumLevel
- {
- get { return _logLevel; }
- set {_logLevel = value; }
- }
-
- public void AddProvider(ILoggerProvider provider)
- {
- }
-
- public ILogger CreateLogger(string categoryName)
- {
- return _logger;
- }
-
- public CustomLogger Logger { get { return _logger; } }
- }
-
- ///
- /// Processing a requires 'unprotecting' the state.
- /// This class side-steps that process.
- ///
- public class AuthenticationPropertiesFormater : ISecureDataFormat
- {
- public string Protect(AuthenticationProperties data)
- {
- return "protectedData";
- }
-
- AuthenticationProperties ISecureDataFormat.Unprotect(string protectedText)
- {
- return new AuthenticationProperties();
- }
- }
-
- ///
- /// Used to set up different configurations of metadata for different tests
- ///
- public class ConfigurationManager
- {
- ///
- /// Simple static empty manager.
- ///
- static public IConfigurationManager DefaultStaticConfigurationManager
- {
- get { return new StaticConfigurationManager(new OpenIdConnectConfiguration()); }
- }
- }
}
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs
index 32aa028f0..52ac42286 100644
--- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs
@@ -4,26 +4,22 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
-using System.Text;
using System.Threading.Tasks;
-using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNet.Authentication.Cookies;
-using Microsoft.AspNet.Authentication.DataHandler;
using Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Builder;
-using Microsoft.AspNet.DataProtection;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.WebEncoders;
-using Newtonsoft.Json;
+using Microsoft.IdentityModel.Protocols;
+using Moq;
using Shouldly;
using Xunit;
@@ -33,25 +29,31 @@ public class OpenIdConnectMiddlewareTests
{
static string noncePrefix = "OpenIdConnect." + "Nonce.";
static string nonceDelimiter = ".";
+ const string Challenge = "/challenge";
+ const string ChallengeWithOutContext = "/challengeWithOutContext";
+ const string ChallengeWithProperties = "/challengeWithProperties";
+ const string DefaultHost = @"https://example.com";
+ const string DefaultAuthority = @"https://example.com/common";
+ const string ExpectedAuthorizeRequest = @"https://example.com/common/oauth2/signin";
+ const string ExpectedLogoutRequest = @"https://example.com/common/oauth2/logout";
+ const string Logout = "/logout";
+ const string Signin = "/signin";
+ const string Signout = "/signout";
[Fact]
- public async Task ChallengeWillTriggerRedirect()
+ public async Task ChallengeWillSetDefaults()
{
+ var stateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
+ var queryValues = ExpectedQueryValues.Defaults(DefaultAuthority);
+ queryValues.State = OpenIdConnectAuthenticationDefaults.AuthenticationPropertiesKey + "=" + stateDataFormat.Protect(new AuthenticationProperties());
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
- options.ClientId = "Test Id";
- options.SignInScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;
+ SetOptions(options, DefaultParameters(), queryValues);
});
- var transaction = await SendAsync(server, "https://example.com/challenge");
+
+ var transaction = await SendAsync(server, DefaultHost + Challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
- var location = transaction.Response.Headers.Location.ToString();
- location.ShouldContain("https://login.windows.net/common/oauth2/authorize?");
- location.ShouldContain("client_id=");
- location.ShouldContain("&response_type=");
- location.ShouldContain("&scope=");
- location.ShouldContain("&state=");
- location.ShouldContain("&response_mode=");
+ queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
[Fact]
@@ -59,95 +61,230 @@ public async Task ChallengeWillSetNonceCookie()
{
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
+ options.Authority = DefaultAuthority;
options.ClientId = "Test Id";
+ options.Configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
});
- var transaction = await SendAsync(server, "https://example.com/challenge");
- transaction.SetCookie.Single().ShouldContain("OpenIdConnect.nonce.");
+ var transaction = await SendAsync(server, DefaultHost + Challenge);
+ transaction.SetCookie.Single().ShouldContain(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix);
}
[Fact]
- public async Task ChallengeWillSetDefaultScope()
+ public async Task ChallengeWillUseOptionsProperties()
{
+ var queryValues = new ExpectedQueryValues(DefaultAuthority);
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
- options.ClientId = "Test Id";
+ SetOptions(options, DefaultParameters(), queryValues);
});
- var transaction = await SendAsync(server, "https://example.com/challenge");
+
+ var transaction = await SendAsync(server, DefaultHost + Challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
- transaction.Response.Headers.Location.Query.ShouldContain("&scope=" + UrlEncoder.Default.UrlEncode("openid profile"));
+ queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
- [Fact]
- public async Task ChallengeWillUseOptionsProperties()
+ ///
+ /// Tests RedirectToIdentityProviderNotification replaces the OpenIdConnectMesssage correctly.
+ ///
+ /// Task
+ [Theory]
+ [InlineData(Challenge, OpenIdConnectRequestType.AuthenticationRequest)]
+ [InlineData(Signout, OpenIdConnectRequestType.LogoutRequest)]
+ public async Task ChallengeSettingMessage(string challenge, OpenIdConnectRequestType requestType)
+ {
+ var configuration = new OpenIdConnectConfiguration
+ {
+ AuthorizationEndpoint = ExpectedAuthorizeRequest,
+ EndSessionEndpoint = ExpectedLogoutRequest
+ };
+
+ var queryValues = new ExpectedQueryValues(DefaultAuthority, configuration)
+ {
+ RequestType = requestType
+ };
+ var server = CreateServer(SetProtocolMessageOptions);
+ var transaction = await SendAsync(server, DefaultHost + challenge);
+ transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
+ queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, new string[] {});
+ }
+
+ private static void SetProtocolMessageOptions(OpenIdConnectAuthenticationOptions options)
{
+ var mockOpenIdConnectMessage = new Mock();
+ mockOpenIdConnectMessage.Setup(m => m.CreateAuthenticationRequestUrl()).Returns(ExpectedAuthorizeRequest);
+ mockOpenIdConnectMessage.Setup(m => m.CreateLogoutRequestUrl()).Returns(ExpectedLogoutRequest);
+ options.AutomaticAuthentication = true;
+ options.Notifications =
+ new OpenIdConnectAuthenticationNotifications
+ {
+ RedirectToIdentityProvider = (notification) =>
+ {
+ notification.ProtocolMessage = mockOpenIdConnectMessage.Object;
+ return Task.FromResult(null);
+ }
+ };
+ }
+
+ ///
+ /// Tests for users who want to add 'state'. There are two ways to do it.
+ /// 1. Users set 'state' (OpenIdConnectMessage.State) in the notification. The runtime appends to that state.
+ /// 2. Users add to the AuthenticationProperties (notification.AuthenticationProperties), values will be serialized.
+ ///
+ ///
+ ///
+ [Theory, MemberData("StateDataSet")]
+ public async Task ChallengeSettingState(string userState, string challenge)
+ {
+ var queryValues = new ExpectedQueryValues(DefaultAuthority);
+ var stateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
+ var properties = new AuthenticationProperties();
+ if (challenge == ChallengeWithProperties)
+ {
+ properties.Items.Add("item1", Guid.NewGuid().ToString());
+ }
+ else
+ {
+ properties.Items.Add(OpenIdConnectAuthenticationDefaults.RedirectUriForCodePropertiesKey, queryValues.RedirectUri);
+ }
+
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
- options.ClientId = "Test Id";
- options.SignInScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;
- options.Scope = "https://www.googleapis.com/auth/plus.login";
- options.ResponseType = "id_token";
- });
- var transaction = await SendAsync(server, "https://example.com/challenge");
+ SetOptions(options, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }), queryValues, stateDataFormat);
+ options.AutomaticAuthentication = challenge.Equals(ChallengeWithOutContext);
+ options.Notifications = new OpenIdConnectAuthenticationNotifications
+ {
+ RedirectToIdentityProvider = notification =>
+ {
+ notification.ProtocolMessage.State = userState;
+ return Task.FromResult(null);
+ }
+
+ };
+ }, null, properties);
+
+ var transaction = await SendAsync(server, DefaultHost + challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
- var query = transaction.Response.Headers.Location.Query;
- query.ShouldContain("scope=" + UrlEncoder.Default.UrlEncode("https://www.googleapis.com/auth/plus.login"));
- query.ShouldContain("response_type=" + UrlEncoder.Default.UrlEncode("id_token"));
+ queryValues.State = stateDataFormat.Protect(properties);
+ queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }));
+ }
+
+ public static TheoryData StateDataSet
+ {
+ get
+ {
+ var dataset = new TheoryData();
+ dataset.Add(Guid.NewGuid().ToString(), Challenge);
+ dataset.Add(null, Challenge);
+ dataset.Add(Guid.NewGuid().ToString(), ChallengeWithOutContext);
+ dataset.Add(null, ChallengeWithOutContext);
+ dataset.Add(Guid.NewGuid().ToString(), ChallengeWithProperties);
+ dataset.Add(null, ChallengeWithProperties);
+
+ return dataset;
+ }
}
[Fact]
public async Task ChallengeWillUseNotifications()
{
- ISecureDataFormat stateFormat = new PropertiesDataFormat(new EphemeralDataProtectionProvider().CreateProtector("GoogleTest"));
+ var queryValues = new ExpectedQueryValues(DefaultAuthority);
+ var queryValuesSetInNotification = new ExpectedQueryValues(DefaultAuthority);
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
- options.ClientId = "Test Id";
+ SetOptions(options, DefaultParameters(), queryValues);
options.Notifications = new OpenIdConnectAuthenticationNotifications
{
- MessageReceived = notification =>
- {
- notification.ProtocolMessage.Scope = "test openid profile";
- notification.HandleResponse();
- return Task.FromResult(null);
- }
+ RedirectToIdentityProvider = notification =>
+ {
+ notification.ProtocolMessage.ClientId = queryValuesSetInNotification.ClientId;
+ notification.ProtocolMessage.RedirectUri = queryValuesSetInNotification.RedirectUri;
+ notification.ProtocolMessage.Resource = queryValuesSetInNotification.Resource;
+ notification.ProtocolMessage.Scope = queryValuesSetInNotification.Scope;
+ return Task.FromResult(null);
+ }
};
});
- var properties = new AuthenticationProperties();
- var state = stateFormat.Protect(properties);
- var transaction = await SendAsync(server,"https://example.com/challenge");
+ var transaction = await SendAsync(server, DefaultHost + Challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
+ queryValuesSetInNotification.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
+ private void SetOptions(OpenIdConnectAuthenticationOptions options, List parameters, ExpectedQueryValues queryValues, ISecureDataFormat secureDataFormat = null)
+ {
+ foreach (var param in parameters)
+ {
+ if (param.Equals(OpenIdConnectParameterNames.ClientId))
+ options.ClientId = queryValues.ClientId;
+ else if (param.Equals(OpenIdConnectParameterNames.RedirectUri))
+ options.RedirectUri = queryValues.RedirectUri;
+ else if (param.Equals(OpenIdConnectParameterNames.Resource))
+ options.Resource = queryValues.Resource;
+ else if (param.Equals(OpenIdConnectParameterNames.Scope))
+ options.Scope = queryValues.Scope;
+ }
+
+ options.Authority = queryValues.Authority;
+ options.Configuration = queryValues.Configuration;
+ options.StateDataFormat = secureDataFormat ?? new AuthenticationPropertiesFormaterKeyValue();
+ }
+
+ private List DefaultParameters(string[] additionalParams = null)
+ {
+ var parameters =
+ new List
+ {
+ OpenIdConnectParameterNames.ClientId,
+ OpenIdConnectParameterNames.RedirectUri,
+ OpenIdConnectParameterNames.Resource,
+ OpenIdConnectParameterNames.ResponseMode,
+ OpenIdConnectParameterNames.Scope,
+ };
+
+ if (additionalParams != null)
+ parameters.AddRange(additionalParams);
+
+ return parameters;
+ }
+
+ private static void DefaultChallengeOptions(OpenIdConnectAuthenticationOptions options)
+ {
+ options.AuthenticationScheme = "OpenIdConnectHandlerTest";
+ options.AutomaticAuthentication = true;
+ options.ClientId = Guid.NewGuid().ToString();
+ options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
+ options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
+ }
[Fact]
public async Task SignOutWithDefaultRedirectUri()
{
+ var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
+ options.Authority = DefaultAuthority;
options.ClientId = "Test Id";
+ options.Configuration = configuration;
});
- var transaction = await SendAsync(server, "https://example.com/signout");
+ var transaction = await SendAsync(server, DefaultHost + Signout);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
- transaction.Response.Headers.Location.AbsoluteUri.ShouldBe("https://login.windows.net/common/oauth2/logout");
+ transaction.Response.Headers.Location.AbsoluteUri.ShouldBe(configuration.EndSessionEndpoint);
}
[Fact]
public async Task SignOutWithCustomRedirectUri()
{
+ var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
+ options.Authority = DefaultAuthority;
options.ClientId = "Test Id";
+ options.Configuration = configuration;
options.PostLogoutRedirectUri = "https://example.com/logout";
});
- var transaction = await SendAsync(server, "https://example.com/signout");
+ var transaction = await SendAsync(server, DefaultHost + Signout);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
transaction.Response.Headers.Location.AbsoluteUri.ShouldContain(UrlEncoder.Default.UrlEncode("https://example.com/logout"));
}
@@ -155,10 +292,12 @@ public async Task SignOutWithCustomRedirectUri()
[Fact]
public async Task SignOutWith_Specific_RedirectUri_From_Authentication_Properites()
{
+ var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(options =>
{
- options.Authority = "https://login.windows.net/common";
+ options.Authority = DefaultAuthority;
options.ClientId = "Test Id";
+ options.Configuration = configuration;
options.PostLogoutRedirectUri = "https://example.com/logout";
});
@@ -167,53 +306,40 @@ public async Task SignOutWith_Specific_RedirectUri_From_Authentication_Properite
transaction.Response.Headers.Location.AbsoluteUri.ShouldContain(UrlEncoder.Default.UrlEncode("http://www.example.com/specific_redirect_uri"));
}
- [Fact]
- // Test Cases for calculating the expiration time of cookie from cookie name
- public void NonceCookieExpirationTime()
- {
- DateTime utcNow = DateTime.UtcNow;
-
- GetNonceExpirationTime(noncePrefix + DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MaxValue);
-
- GetNonceExpirationTime(noncePrefix + DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue + TimeSpan.FromHours(1));
-
- GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
-
- GetNonceExpirationTime(noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
-
- GetNonceExpirationTime("", TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
-
- GetNonceExpirationTime(noncePrefix + noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
-
- GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
-
- GetNonceExpirationTime(utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
- }
-
- private static TestServer CreateServer(Action configureOptions, Func handler = null)
+ private static TestServer CreateServer(Action configureOptions, Func handler = null, AuthenticationProperties properties = null)
{
return TestServer.Create(app =>
{
app.UseCookieAuthentication(options =>
{
- options.AuthenticationScheme = "OpenIdConnect";
+ options.AuthenticationScheme = OpenIdConnectAuthenticationDefaults.AuthenticationScheme;
});
app.UseOpenIdConnectAuthentication(configureOptions);
app.Use(async (context, next) =>
{
var req = context.Request;
var res = context.Response;
- if (req.Path == new PathString("/challenge"))
+
+ if (req.Path == new PathString(Challenge))
{
- context.Authentication.Challenge("OpenIdConnect");
+ context.Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
res.StatusCode = 401;
}
- else if (req.Path == new PathString("/signin"))
+ else if (req.Path == new PathString(ChallengeWithProperties))
+ {
+ context.Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, properties);
+ res.StatusCode = 401;
+ }
+ else if (req.Path == new PathString(ChallengeWithOutContext))
+ {
+ res.StatusCode = 401;
+ }
+ else if (req.Path == new PathString(Signin))
{
// REVIEW: this used to just be res.SignIn()
- context.Authentication.SignIn("OpenIdConnect", new ClaimsPrincipal());
+ context.Authentication.SignIn(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal());
}
- else if (req.Path == new PathString("/signout"))
+ else if (req.Path == new PathString(Signout))
{
context.Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
}
@@ -250,34 +376,39 @@ private static async Task SendAsync(TestServer server, string uri,
{
request.Headers.Add("Cookie", cookieHeader);
}
+
var transaction = new Transaction
{
Request = request,
Response = await server.CreateClient().SendAsync(request),
};
+
if (transaction.Response.Headers.Contains("Set-Cookie"))
{
transaction.SetCookie = transaction.Response.Headers.GetValues("Set-Cookie").ToList();
}
- transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
+ transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync();
if (transaction.Response.Content != null &&
transaction.Response.Content.Headers.ContentType != null &&
transaction.Response.Content.Headers.ContentType.MediaType == "text/xml")
{
transaction.ResponseElement = XElement.Parse(transaction.ResponseText);
}
+
return transaction;
}
private class Transaction
{
public HttpRequestMessage Request { get; set; }
+
public HttpResponseMessage Response { get; set; }
public IList SetCookie { get; set; }
public string ResponseText { get; set; }
+
public XElement ResponseElement { get; set; }
public string AuthenticationCookieValue
@@ -296,57 +427,29 @@ public string AuthenticationCookieValue
return null;
}
}
-
- public string FindClaimValue(string claimType)
- {
- XElement claim = ResponseElement.Elements("claim").SingleOrDefault(elt => elt.Attribute("type").Value == claimType);
- if (claim == null)
- {
- return null;
- }
- return claim.Attribute("value").Value;
- }
- }
- private static void Describe(HttpResponse res, ClaimsIdentity identity)
- {
- res.StatusCode = 200;
- res.ContentType = "text/xml";
- var xml = new XElement("xml");
- if (identity != null)
- {
- xml.Add(identity.Claims.Select(claim => new XElement("claim", new XAttribute("type", claim.Type), new XAttribute("value", claim.Value))));
- }
- using (var memory = new MemoryStream())
- {
- using (var writer = new XmlTextWriter(memory, Encoding.UTF8))
- {
- xml.WriteTo(writer);
- }
- res.Body.Write(memory.ToArray(), 0, memory.ToArray().Length);
- }
}
- private class TestHttpMessageHandler : HttpMessageHandler
+ [Fact]
+ // Test Cases for calculating the expiration time of cookie from cookie name
+ public void NonceCookieExpirationTime()
{
- public Func Sender { get; set; }
+ DateTime utcNow = DateTime.UtcNow;
- protected override Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
- {
- if (Sender != null)
- {
- return Task.FromResult(Sender(request));
- }
+ GetNonceExpirationTime(noncePrefix + DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MaxValue);
- return Task.FromResult(null);
- }
- }
+ GetNonceExpirationTime(noncePrefix + DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue + TimeSpan.FromHours(1));
- private static HttpResponseMessage ReturnJsonResponse(object content)
- {
- var res = new HttpResponseMessage(HttpStatusCode.OK);
- var text = JsonConvert.SerializeObject(content);
- res.Content = new StringContent(text, Encoding.UTF8, "application/json");
- return res;
+ GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
+
+ GetNonceExpirationTime(noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
+
+ GetNonceExpirationTime("", TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
+
+ GetNonceExpirationTime(noncePrefix + noncePrefix, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
+
+ GetNonceExpirationTime(noncePrefix + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(utcNow + TimeSpan.FromHours(1));
+
+ GetNonceExpirationTime(utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter + utcNow.Ticks.ToString(CultureInfo.InvariantCulture) + nonceDelimiter, TimeSpan.FromHours(1)).ShouldBe(DateTime.MinValue);
}
private static DateTime GetNonceExpirationTime(string keyname, TimeSpan nonceLifetime)
@@ -375,8 +478,8 @@ private static DateTime GetNonceExpirationTime(string keyname, TimeSpan nonceLif
}
}
}
+
return nonceTime;
}
-
}
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/TestUtilities.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/TestUtilities.cs
index 1e6bea793..13479fdce 100644
--- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/TestUtilities.cs
+++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/TestUtilities.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.IdentityModel.Protocols;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
@@ -10,6 +11,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
///
public class TestUtilities
{
+ public const string DefaultHost = @"http://localhost";
+
public static bool AreEqual(object obj1, object obj2, Func comparer = null) where T : class
{
if (obj1 == null && obj2 == null)
@@ -63,11 +66,6 @@ private static bool AreEqual(LogEntry logEntry1, LogEntry logEntry2)
return false;
}
- if (!AreEqual(logEntry1.Exception, logEntry2.Exception))
- {
- return false;
- }
-
if (logEntry1.State == null && logEntry2.State == null)
{
return true;
@@ -104,5 +102,26 @@ private static bool AreEqual(Exception exception1, Exception exception2)
return AreEqual(exception1.InnerException, exception2.InnerException);
}
+
+ static public IConfigurationManager DefaultOpenIdConnectConfigurationManager
+ {
+ get
+ {
+ return new StaticConfigurationManager(DefaultOpenIdConnectConfiguration);
+ }
+ }
+
+ static public OpenIdConnectConfiguration DefaultOpenIdConnectConfiguration
+ {
+ get
+ {
+ return new OpenIdConnectConfiguration()
+ {
+ AuthorizationEndpoint = @"https://login.windows.net/common/oauth2/authorize",
+ EndSessionEndpoint = @"https://login.windows.net/common/oauth2/endsessionendpoint",
+ TokenEndpoint = @"https://login.windows.net/common/oauth2/token",
+ };
+ }
+ }
}
}