#214 Refactor OIDC state parameters.

This commit is contained in:
Chris R 2015-07-08 12:21:00 -07:00
parent 039cc18e8b
commit 57031946d0
16 changed files with 1350 additions and 818 deletions

View File

@ -9,14 +9,14 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public static class OpenIdConnectAuthenticationDefaults
{
/// <summary>
/// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationScheme
/// Constant used to identify state in openIdConnect protocol message.
/// </summary>
public const string AuthenticationScheme = "OpenIdConnect";
public const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
/// <summary>
/// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName
/// The default value used for OpenIdConnectAuthenticationOptions.AuthenticationScheme.
/// </summary>
public const string CookiePrefix = ".AspNet.OpenIdConnect.";
public const string AuthenticationScheme = "OpenIdConnect";
/// <summary>
/// The default value for OpenIdConnectAuthenticationOptions.Caption.
@ -24,18 +24,23 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
public const string Caption = "OpenIdConnect";
/// <summary>
/// The prefix used to for the a nonce in the cookie
/// The prefix used to provide a default OpenIdConnectAuthenticationOptions.CookieName.
/// </summary>
public const string CookiePrefix = ".AspNet.OpenIdConnect.";
/// <summary>
/// The prefix used to for the a nonce in the cookie.
/// </summary>
public const string CookieNoncePrefix = ".AspNet.OpenIdConnect.Nonce.";
/// <summary>
/// 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'.
/// </summary>
public const string RedirectUriUsedForCodeKey = "OpenIdConnect.Code.RedirectUri";
public const string RedirectUriForCodePropertiesKey = "OpenIdConnect.Code.RedirectUri";
/// <summary>
/// Constant used to identify state in openIdConnect protocal message
/// Constant used to identify userstate inside AuthenticationProperties that have been serialized in the 'state' parameter.
/// </summary>
public const string AuthenticationPropertiesKey = "OpenIdConnect.AuthenticationProperties";
public const string UserstatePropertiesKey = "OpenIdConnect.Userstate";
}
}

View File

@ -28,19 +28,6 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
private const string UriSchemeDelimiter = "://";
private OpenIdConnectConfiguration _configuration;
private string CurrentUri
{
get
{
return Request.Scheme +
UriSchemeDelimiter +
Request.Host +
Request.PathBase +
Request.Path +
Request.QueryString;
}
}
/// <summary>
/// Handles Signout
/// </summary>
@ -54,7 +41,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
var openIdConnectMessage = new OpenIdConnectMessage()
var message = new OpenIdConnectMessage()
{
IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty),
RequestType = OpenIdConnectRequestType.LogoutRequest,
@ -66,30 +53,42 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var properties = new AuthenticationProperties(signout.Properties);
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = properties.RedirectUri;
message.PostLogoutRedirectUri = properties.RedirectUri;
}
else if (!string.IsNullOrWhiteSpace(Options.PostLogoutRedirectUri))
else if (!string.IsNullOrEmpty(Options.PostLogoutRedirectUri))
{
openIdConnectMessage.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
message.PostLogoutRedirectUri = Options.PostLogoutRedirectUri;
}
var notification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
if (Options.Notifications.RedirectToIdentityProvider != null)
{
ProtocolMessage = openIdConnectMessage
};
await Options.Notifications.RedirectToIdentityProvider(notification);
if (!notification.HandledResponse)
{
var redirectUri = notification.ProtocolMessage.CreateLogoutRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
ProtocolMessage = 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;
}
Response.Redirect(redirectUri);
message = redirectToIdentityProviderNotification.ProtocolMessage;
}
var redirectUri = message.CreateLogoutRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
{
Logger.LogWarning(Resources.OIDCH_0051_RedirectUriLogoutIsNotWellFormed, redirectUri);
}
Response.Redirect(redirectUri);
}
}
@ -107,7 +106,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
// 2. CurrentUri if Options.DefaultToCurrentUriOnRedirect is true)
AuthenticationProperties properties = new AuthenticationProperties(context.Properties);
if (!string.IsNullOrWhiteSpace(properties.RedirectUri))
if (!string.IsNullOrEmpty(properties.RedirectUri))
{
Logger.LogDebug(Resources.OIDCH_0030_Using_Properties_RedirectUri, properties.RedirectUri);
}
@ -117,15 +116,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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)
@ -138,13 +137,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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)
@ -169,24 +167,37 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
}
var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
if (Options.Notifications.RedirectToIdentityProvider != null)
{
ProtocolMessage = message
};
var redirectToIdentityProviderNotification =
new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
ProtocolMessage = message
};
await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
if (redirectToIdentityProviderNotification.HandledResponse)
{
Logger.LogInformation(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return true; // REVIEW: Make sure this should stop all other handlers
}
else if (redirectToIdentityProviderNotification.Skipped)
{
Logger.LogInformation(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return false; // REVIEW: Make sure this should not stop all other handlers
await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
if (redirectToIdentityProviderNotification.HandledResponse)
{
Logger.LogVerbose(Resources.OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse);
return true;
}
else if (redirectToIdentityProviderNotification.Skipped)
{
Logger.LogVerbose(Resources.OIDCH_0035_RedirectToIdentityProviderNotificationSkipped);
return false;
}
if (!string.IsNullOrEmpty(redirectToIdentityProviderNotification.ProtocolMessage.State))
{
properties.Items[OpenIdConnectAuthenticationDefaults.UserstatePropertiesKey] = redirectToIdentityProviderNotification.ProtocolMessage.State;
}
message = redirectToIdentityProviderNotification.ProtocolMessage;
}
var redirectUri = redirectToIdentityProviderNotification.ProtocolMessage.CreateAuthenticationRequestUrl();
message.State = Options.StateDataFormat.Protect(properties);
var redirectUri = message.CreateAuthenticationRequestUrl();
if (!Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute))
{
Logger.LogWarning(Resources.OIDCH_0036_UriIsNotWellFormed, redirectUri);
@ -246,66 +257,74 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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);
}
else
{
// 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;
}
var properties = GetPropertiesFromState(message.State);
if (properties == null)
// if any of the error fields are set, throw error null
if (!string.IsNullOrEmpty(message.Error))
{
Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid);
return null;
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"));
}
// devs will need to hook AuthenticationFailedNotification to avoid having 'raw' runtime errors displayed to users.
if (!string.IsNullOrWhiteSpace(message.Error))
if (_configuration == null && Options.ConfigurationManager != null)
{
Logger.LogError(Resources.OIDCH_0006_MessageErrorNotNull, message.Error);
throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageErrorNotNull, message.Error));
Logger.LogVerbose(Resources.OIDCH_0007_UpdatingConfiguration);
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
AuthenticationTicket ticket = null;
JwtSecurityToken jwt = null;
if (_configuration == null && Options.ConfigurationManager != null)
{
Logger.LogDebug(Resources.OIDCH_0007_UpdatingConfiguration);
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
}
// 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<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(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;
}
@ -313,11 +332,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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 };
}
@ -336,7 +355,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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()));
}
}
}
@ -344,16 +363,16 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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;
}
@ -384,13 +403,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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;
}
@ -416,7 +435,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var protocolValidationContext = new OpenIdConnectProtocolValidationContext
{
AuthorizationCode = message.Code,
Nonce = nonce,
Nonce = nonce,
};
Options.ProtocolValidator.Validate(jwt, protocolValidationContext);
@ -424,7 +443,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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);
@ -436,20 +455,20 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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;
}
}
@ -463,7 +482,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
// 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 =
@ -476,13 +499,13 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
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;
}

View File

@ -93,7 +93,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0027: converted 401 to 403.
/// OIDCH_0027: Converted 401 to 403.
/// </summary>
internal static string OIDCH_0027_401_ConvertedTo_403
{
@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
/// OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.
/// </summary>
internal static string OIDCH_0030_Using_Properties_RedirectUri
{
@ -125,7 +125,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.
/// OIDCH_0031: Using Options.RedirectUri for 'redirect_uri': '{0}'.
/// </summary>
internal static string OIDCH_0031_Using_Options_RedirectUri
{
@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.
/// OIDCH_0032: Using the CurrentUri for 'local redirect' post authentication: '{0}'.
/// </summary>
internal static string OIDCH_0032_UsingCurrentUriRedirectUri
{
@ -145,11 +145,11 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
internal static string OIDCH_0033_NonceAlreadyExists
{
get { return ResourceManager.GetString("OIDCH_0033_NonceAlreadyExists"); }
get { return ResourceManager.GetString("OIDCH_0033_NonceAlreadyExists"); }
}
/// <summary>
/// OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse
/// OIDCH_0034: RedirectToIdentityProviderNotification.HandledResponse
/// </summary>
internal static string OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse
{
@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0035: redirectToIdentityProviderNotification.Skipped
/// OIDCH_0035: RedirectToIdentityProviderNotification.Skipped
/// </summary>
internal static string OIDCH_0035_RedirectToIdentityProviderNotificationSkipped
{
@ -165,13 +165,21 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// 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}'.)
/// </summary>
internal static string OIDCH_0036_UriIsNotWellFormed
{
get { return ResourceManager.GetString("OIDCH_0036_UriIsNotWellFormed"); }
}
/// <summary>
/// OIDCH_0036: RedirectUri is: '{0}'.
/// </summary>
internal static string OIDCH_0037_RedirectUri
{
get { return ResourceManager.GetString("OIDCH_0037_RedirectUri"); }
}
/// <summary>
/// OIDCH_0000: Entering: '{0}'.
/// </summary>
@ -197,7 +205,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0002: messageReceivedNotification.HandledResponse
/// OIDCH_0002: MessageReceivedNotification.HandledResponse
/// </summary>
internal static string OIDCH_0002_MessageReceivedNotificationHandledResponse
{
@ -205,7 +213,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0003: messageReceivedNotification.Skipped
/// OIDCH_0003: MessageReceivedNotification.Skipped
/// </summary>
internal static string OIDCH_0003_MessageReceivedNotificationSkipped
{
@ -213,15 +221,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// 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.
/// </summary>
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"); }
}
/// <summary>
/// OIDCH_0005: unable to unprotect the message.State
/// OIDCH_0005: Unable to unprotect the message.State
/// </summary>
internal static string OIDCH_0005_MessageStateIsInvalid
{
@ -229,15 +237,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0006_MessageErrorNotNull: '{0}'.
/// OIDCH_0006: Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.
/// </summary>
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"); }
}
/// <summary>
/// OIDCH_0007: updating configuration
/// OIDCH_0007: Updating configuration
/// </summary>
internal static string OIDCH_0007_UpdatingConfiguration
{
@ -245,7 +253,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0008: securityTokenReceivedNotification.HandledResponse
/// OIDCH_0008: SecurityTokenReceivedNotification.HandledResponse
/// </summary>
internal static string OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse
{
@ -253,7 +261,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0009: securityTokenReceivedNotification.Skipped
/// OIDCH_0009: SecurityTokenReceivedNotification.Skipped
/// </summary>
internal static string OIDCH_0009_SecurityTokenReceivedNotificationSkipped
{
@ -269,7 +277,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// 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}'.
/// </summary>
internal static string OIDCH_0011_UnableToValidateToken
{
@ -277,7 +285,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0012: securityTokenValidatedNotification.HandledResponse
/// OIDCH_0012: SecurityTokenValidatedNotification.HandledResponse
/// </summary>
internal static string OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse
{
@ -285,7 +293,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0013: securityTokenValidatedNotification.Skipped
/// OIDCH_0013: SecurityTokenValidatedNotification.Skipped
/// </summary>
internal static string OIDCH_0013_SecurityTokenValidatedNotificationSkipped
{
@ -293,31 +301,31 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0014: 'code' received: '{0}'
/// OIDCH_0014: AuthorizationCode received: '{0}'
/// </summary>
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"); }
}
/// <summary>
/// OIDCH_0015: codeReceivedNotification.HandledResponse")
/// OIDCH_0015: AuthorizationCodeReceivedNotification.HandledResponse
/// </summary>
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"); }
}
/// <summary>
/// OIDCH_0016: codeReceivedNotification.Skipped
/// </summary>
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"); }
}
/// <summary>
/// OIDCH_0017: Exception occurred while processing message
/// OIDCH_0017: Exception occurred while processing message.
/// </summary>
internal static string OIDCH_0017_ExceptionOccurredWhileProcessingMessage
{
@ -341,11 +349,20 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
/// <summary>
/// OIDCH_0020: 'id_token' received: '{0}'
/// OIDCH_0020: 'id_token' received: '{0}'.
/// </summary>
internal static string OIDCH_0020_IdTokenReceived
{
get { return ResourceManager.GetString("OIDCH_0020_IdTokenReceived"); }
}
/// <summary>
/// OIDCH_0021: exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.
/// </summary>
internal static string OIDCH_0021_AutomaticConfigurationRefresh
{
get { return ResourceManager.GetString("OIDCH_0021_AutomaticConfigurationRefresh"); }
}
}
}

View File

@ -130,7 +130,7 @@
<value>OIDCH_0026: Entering: '{0}'</value>
</data>
<data name="OIDCH_0027_401_ConvertedTo_403" xml:space="preserve">
<value>OIDCH_0027: converted 401 to 403.</value>
<value>OIDCH_0027: Converted 401 to 403.</value>
</data>
<data name="OIDCH_0028_StatusCodeNot401" xml:space="preserve">
<value>OIDCH_0028: Response.StatusCode != 401, StatusCode: '{0}'.</value>
@ -139,10 +139,10 @@
<value>OIDCH_0029: ChallengeContext == null AND !Options.AutomaticAuthentication</value>
</data>
<data name="OIDCH_0030_Using_Properties_RedirectUri" xml:space="preserve">
<value>OIDCH_0030: using properties.RedirectUri for 'local redirect' post authentication: '{0}'.</value>
<value>OIDCH_0030: Using properties.RedirectUri for 'local redirect' post authentication: '{0}'.</value>
</data>
<data name="OIDCH_0031_Using_Options_RedirectUri" xml:space="preserve">
<value>OIDCH_0031: using Options.RedirectUri for 'redirect_uri': '{0}'.</value>
<value>OIDCH_0031: Using Options.RedirectUri for 'redirect_uri': '{0}'.</value>
</data>
<data name="OIDCH_0032_UsingCurrentUriRedirectUri" xml:space="preserve">
<value>OIDCH_0032: using the CurrentUri for 'local redirect' post authentication: '{0}'.</value>
@ -151,13 +151,16 @@
<value>OIDCH_0033: ProtocolValidator.RequireNonce == true. The generated nonce already exists: this usually indicates the nonce is not unique or has been used. The nonce is: '{0}'.</value>
</data>
<data name="OIDCH_0034_RedirectToIdentityProviderNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0034: redirectToIdentityProviderNotification.HandledResponse</value>
<value>OIDCH_0034: RedirectToIdentityProviderNotification.HandledResponse</value>
</data>
<data name="OIDCH_0035_RedirectToIdentityProviderNotificationSkipped" xml:space="preserve">
<value>OIDCH_0035: redirectToIdentityProviderNotification.Skipped</value>
<value>OIDCH_0035: RedirectToIdentityProviderNotification.Skipped</value>
</data>
<data name="OIDCH_0036_UriIsNotWellFormed" xml:space="preserve">
<value>OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: {0}", (redirectUri ?? "null"))</value>
<value>OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: '{0}'.</value>
</data>
<data name="OIDCH_0037_RedirectUri" xml:space="preserve">
<value>OIDCH_0037: RedirectUri is: '{0}'.</value>
</data>
<data name="OIDCH_0000_AuthenticateCoreAsync" xml:space="preserve">
<value>OIDCH_0000: Entering: '{0}'.</value>
@ -166,60 +169,63 @@
<value>OIDCH_0001: MessageReceived: '{0}'.</value>
</data>
<data name="OIDCH_0002_MessageReceivedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0002: messageReceivedNotification.HandledResponse</value>
<value>OIDCH_0002: MessageReceivedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0003_MessageReceivedNotificationSkipped" xml:space="preserve">
<value>OIDCH_0003: messageReceivedNotification.Skipped</value>
<value>OIDCH_0003: MessageReceivedNotification.Skipped</value>
</data>
<data name="OIDCH_0004_MessageStateIsNullOrWhiteSpace" xml:space="preserve">
<value>OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or whitespace. State is required to process the message.</value>
<data name="OIDCH_0004_MessageStateIsNullOrEmpty" xml:space="preserve">
<value>OIDCH_0004: OpenIdConnectAuthenticationHandler: message.State is null or empty.</value>
</data>
<data name="OIDCH_0005_MessageStateIsInValid" xml:space="preserve">
<value>OIDCH_0005: unable to unprotect the message.State</value>
<data name="OIDCH_0005_MessageStateIsInvalid" xml:space="preserve">
<value>OIDCH_0005: Unable to unprotect the message.State.</value>
</data>
<data name="OIDCH_0006_MessageErrorNotNull" xml:space="preserve">
<value>OIDCH_0006_MessageErrorNotNull: '{0}'.</value>
<data name="OIDCH_0006_MessageContainsError" xml:space="preserve">
<value>OIDCH_0006: Message contains error: '{0}', error_description: '{1}', error_uri: '{2}'.</value>
</data>
<data name="OIDCH_0007_UpdatingConfiguration" xml:space="preserve">
<value>OIDCH_0007: updating configuration</value>
<value>OIDCH_0007: Updating configuration</value>
</data>
<data name="OIDCH_0008_SecurityTokenReceivedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0008: securityTokenReceivedNotification.HandledResponse</value>
<value>OIDCH_0008: SecurityTokenReceivedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0009_SecurityTokenReceivedNotificationSkipped:" xml:space="preserve">
<value>OIDCH_0009: securityTokenReceivedNotification.Skipped</value>
<value>OIDCH_0009: SecurityTokenReceivedNotification.Skipped</value>
</data>
<data name="OIDCH_0010_ValidatedSecurityTokenNotJwt" xml:space="preserve">
<value>OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'.</value>
</data>
<data name="OIDCH_0011_UnableToValidateToken" xml:space="preserve">
<value>OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: {0}."</value>
<value>OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: '{0}'."</value>
</data>
<data name="OIDCH_0012_SecurityTokenValidatedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0012: securityTokenValidatedNotification.HandledResponse</value>
<value>OIDCH_0012: SecurityTokenValidatedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0013_SecurityTokenValidatedNotificationSkipped" xml:space="preserve">
<value>OIDCH_0013: securityTokenValidatedNotification.Skipped</value>
<value>OIDCH_0013: SecurityTokenValidatedNotification.Skipped</value>
</data>
<data name="OIDCH_0014_CodeReceived" xml:space="preserve">
<value>OIDCH_0014: 'code' received: '{0}'</value>
<data name="OIDCH_0014_AuthorizationCodeReceived" xml:space="preserve">
<value>OIDCH_0014: AuthorizationCode received: '{0}'.</value>
</data>
<data name="OIDCH_0015_CodeReceivedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0015: codeReceivedNotification.HandledResponse</value>
<data name="OIDCH_0015_AuthorizationCodeReceivedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0015: AuthorizationCodeReceivedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0016_CodeReceivedNotificationSkipped" xml:space="preserve">
<value>OIDCH_0016: codeReceivedNotification.Skipped</value>
<data name="OIDCH_0016_AuthorizationCodeReceivedNotificationSkipped" xml:space="preserve">
<value>OIDCH_0016: AuthorizationCodeReceivedNotification.Skipped</value>
</data>
<data name="OIDCH_0017: Exception occurred while processing message" xml:space="preserve">
<value>OIDCH_0017: Exception occurred while processing message</value>
<data name="OIDCH_0017_ExceptionOccurredWhileProcessingMessage" xml:space="preserve">
<value>OIDCH_0017: Exception occurred while processing message.</value>
</data>
<data name="OIDCH_0018_AuthenticationFailedNotificationHandledResponse" xml:space="preserve">
<value>OIDCH_0018: authenticationFailedNotification.HandledResponse</value>
<value>OIDCH_0018: AuthenticationFailedNotification.HandledResponse</value>
</data>
<data name="OIDCH_0019_AuthenticationNotificationFailedSkipped" xml:space="preserve">
<value>OIDCH_0019: authenticationFailedNotification.Skipped</value>
<value>OIDCH_0019: AuthenticationFailedNotification.Skipped</value>
</data>
<data name="OIDCH_0020_IdTokenReceived" xml:space="preserve">
<value>OIDCH_0020: 'id_token' received: '{0}'</value>
</data>
</root>
<data name="OIDCH_0021_AutomaticConfigurationRefresh" xml:space="preserve">
<value>OIDCH_0021: exception of type 'SecurityTokenSignatureKeyNotFoundException' thrown, Options.ConfigurationManager.RequestRefresh() called.</value>
</data>
</root>

View File

@ -1,16 +1,28 @@
// 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
{
/// <summary>
/// When a user configures the <see cref="AuthenticationMiddleware{TOptions}"/> to be notified prior to redirecting to an IdentityProvider
/// an instance of <see cref="RedirectFromIdentityProviderNotification{TMessage, TOptions, TMessage}"/> is passed to the 'RedirectToIdentityProviderNotification".
/// </summary>
/// <typeparam name="TMessage">protocol specific message.</typeparam>
/// <typeparam name="TOptions">protocol specific options.</typeparam>
public class RedirectToIdentityProviderNotification<TMessage, TOptions> : BaseNotification<TOptions>
{
public RedirectToIdentityProviderNotification(HttpContext context, TOptions options) : base(context, options)
public RedirectToIdentityProviderNotification([NotNull] HttpContext context, [NotNull] TOptions options) : base(context, options)
{
}
public TMessage ProtocolMessage { get; set; }
/// <summary>
/// Gets or sets the <see cref="{TMessage}"/>.
/// </summary>
/// <exception cref="ArgumentNullException">if 'value' is null.</exception>
public TMessage ProtocolMessage { get; [param: NotNull] set; }
}
}
}

View File

@ -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
{
/// <summary>
/// This formatter creates an easy to read string of the format: "'key1' 'value1' ..."
/// </summary>
public class AuthenticationPropertiesFormaterKeyValue : ISecureDataFormat<AuthenticationProperties>
{
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<AuthenticationProperties>.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;
}
}
}

View File

@ -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
{
/// <summary>
/// This helper class is used to check that query string parameters are as expected.
/// </summary>
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<string> parameters)
{
var errors = new List<string>();
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); }
}
}
}

View File

@ -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<LogEntry> _logEntries = new List<LogEntry>();
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<object, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
var logEntry =
new LogEntry
{
EventId = eventId,
Exception = exception,
Formatter = formatter,
Level = logLevel,
State = state,
};
_logEntries.Add(logEntry);
Debug.WriteLine(logEntry.ToString());
}
}
public List<LogEntry> Logs { get { return _logEntries; } }
}
}

View File

@ -0,0 +1,36 @@
// 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 Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
public class InMemoryLoggerFactory : ILoggerFactory
{
InMemoryLogger _logger;
LogLevel _logLevel = LogLevel.Debug;
public InMemoryLoggerFactory(LogLevel logLevel)
{
_logLevel = logLevel;
_logger = new InMemoryLogger(_logLevel);
}
public LogLevel MinimumLevel
{
get { return _logLevel; }
set { _logLevel = value; }
}
public void AddProvider(ILoggerProvider provider)
{
}
public ILogger CreateLogger(string categoryName)
{
return _logger;
}
public InMemoryLogger Logger { get { return _logger; } }
}
}

View File

@ -0,0 +1,41 @@
// 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.
// this controls if the logs are written to the console.
// they can be reviewed for general content.
using System;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
public class LogEntry
{
public LogEntry() { }
public int EventId { get; set; }
public Exception Exception { get; set; }
public Func<object, Exception, string> 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;
}
}
}
}

View File

@ -0,0 +1,142 @@
// 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.
// this controls if the logs are written to the console.
// they can be reviewed for general content.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
public class LoggingUtilities
{
static List<LogEntry> CompleteLogEntries;
static Dictionary<string, LogLevel> LogEntries;
static LoggingUtilities()
{
LogEntries =
new Dictionary<string, LogLevel>()
{
{ "OIDCH_0000:", LogLevel.Debug },
{ "OIDCH_0001:", LogLevel.Debug },
{ "OIDCH_0002:", LogLevel.Verbose },
{ "OIDCH_0003:", LogLevel.Verbose },
{ "OIDCH_0004:", LogLevel.Warning },
{ "OIDCH_0005:", LogLevel.Error },
{ "OIDCH_0006:", LogLevel.Error },
{ "OIDCH_0007:", LogLevel.Verbose },
{ "OIDCH_0008:", LogLevel.Verbose },
{ "OIDCH_0009:", LogLevel.Verbose },
{ "OIDCH_0010:", LogLevel.Error },
{ "OIDCH_0011:", LogLevel.Error },
{ "OIDCH_0012:", LogLevel.Verbose },
{ "OIDCH_0013:", LogLevel.Verbose },
{ "OIDCH_0014:", LogLevel.Debug },
{ "OIDCH_0015:", LogLevel.Verbose },
{ "OIDCH_0016:", LogLevel.Verbose },
{ "OIDCH_0017:", LogLevel.Error },
{ "OIDCH_0018:", LogLevel.Verbose },
{ "OIDCH_0019:", LogLevel.Verbose },
{ "OIDCH_0020:", LogLevel.Debug },
{ "OIDCH_0021:", LogLevel.Verbose },
{ "OIDCH_0026:", LogLevel.Error },
};
BuildLogEntryList();
}
/// <summary>
/// Builds the complete list of OpenIdConnect log entries that are available in the runtime.
/// </summary>
private static void BuildLogEntryList()
{
CompleteLogEntries = new List<LogEntry>();
foreach (var entry in LogEntries)
{
CompleteLogEntries.Add(new LogEntry { State = entry.Key, Level = entry.Value });
}
}
/// <summary>
/// Adds to errors if a variation if any are found.
/// </summary>
/// <param name="variation">if this has been seen before, errors will be appended, test results are easier to understand if this is unique.</param>
/// <param name="capturedLogs">these are the logs the runtime generated</param>
/// <param name="expectedLogs">these are the errors that were expected</param>
/// <param name="errors">the dictionary to record any errors</param>
public static void CheckLogs(List<LogEntry> capturedLogs, List<LogEntry> expectedLogs, List<Tuple<LogEntry, LogEntry>> errors)
{
if (capturedLogs.Count >= expectedLogs.Count)
{
for (int i = 0; i < capturedLogs.Count; i++)
{
if (i + 1 > expectedLogs.Count)
{
errors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], null));
}
else
{
if (!TestUtilities.AreEqual<LogEntry>(capturedLogs[i], expectedLogs[i]))
{
errors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], expectedLogs[i]));
}
}
}
}
else
{
for (int i = 0; i < expectedLogs.Count; i++)
{
if (i + 1 > capturedLogs.Count)
{
errors.Add(new Tuple<LogEntry, LogEntry>(null, expectedLogs[i]));
}
else
{
if (!TestUtilities.AreEqual<LogEntry>(expectedLogs[i], capturedLogs[i]))
{
errors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], expectedLogs[i]));
}
}
}
}
}
public static string LoggingErrors(List<Tuple<LogEntry, LogEntry>> errors)
{
string loggingErrors = null;
if (errors.Count > 0)
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("");
foreach (var error in errors)
{
stringBuilder.AppendLine("*Captured*, *Expected* : *" + (error.Item1?.ToString() ?? "null") + "*, *" + (error.Item2?.ToString() ?? "null") + "*");
}
loggingErrors = stringBuilder.ToString();
}
return loggingErrors;
}
/// <summary>
/// Populates a list of expected log entries for a test variation.
/// </summary>
/// <param name="items">the index for the <see cref="LogEntry"/> in CompleteLogEntries of interest.</param>
/// <returns>a <see cref="List{LogEntry}"/> that represents the expected entries for a test variation.</returns>
public static List<LogEntry> PopulateLogEntries(int[] items)
{
var entries = new List<LogEntry>();
foreach (var item in items)
{
entries.Add(CompleteLogEntries[item]);
}
return entries;
}
}
}

View File

@ -0,0 +1,41 @@
// 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.Threading.Tasks;
using Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Http.Features.Authentication;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
/// <summary>
/// Allows for custom processing of ApplyResponseChallenge, ApplyResponseGrant and AuthenticateCore
/// </summary>
public class OpenIdConnectAuthenticationHandlerForTestingAuthenticate : OpenIdConnectAuthenticationHandler
{
public OpenIdConnectAuthenticationHandlerForTestingAuthenticate()
: base()
{
}
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
return await base.HandleUnauthorizedAsync(context);
}
protected override Task HandleSignInAsync(SignInContext context)
{
return Task.FromResult(0);
}
protected override Task HandleSignOutAsync(SignOutContext context)
{
return Task.FromResult(0);
}
//public override bool ShouldHandleScheme(string authenticationScheme)
//{
// return true;
//}
}
}

View File

@ -0,0 +1,47 @@
// 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 Microsoft.AspNet.Authentication.OpenIdConnect;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.DataProtection;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.WebEncoders;
namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
/// <summary>
/// pass a <see cref="OpenIdConnectAuthenticationHandler"/> as the AuthenticationHandler
/// configured to handle certain messages.
/// </summary>
public class OpenIdConnectAuthenticationMiddlewareForTestingAuthenticate : OpenIdConnectAuthenticationMiddleware
{
OpenIdConnectAuthenticationHandler _handler;
public OpenIdConnectAuthenticationMiddlewareForTestingAuthenticate(
RequestDelegate next,
IDataProtectionProvider dataProtectionProvider,
ILoggerFactory loggerFactory,
IUrlEncoder encoder,
IServiceProvider services,
IOptions<ExternalAuthenticationOptions> externalOptions,
IOptions<OpenIdConnectAuthenticationOptions> options,
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions = null,
OpenIdConnectAuthenticationHandler handler = null
)
: base(next, dataProtectionProvider, loggerFactory, encoder, services, externalOptions, options, configureOptions)
{
_handler = handler;
var customFactory = loggerFactory as InMemoryLoggerFactory;
if (customFactory != null)
Logger = customFactory.Logger;
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return _handler ?? base.CreateHandler();
}
}
}

View File

@ -1,27 +1,25 @@
// 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.
// this controls if the logs are written to the console.
// they can be reviewed for general content.
//#define _Verbose
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Authentication.Notifications;
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.Http.Features.Authentication;
using Microsoft.AspNet.TestHost;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
using Microsoft.Framework.WebEncoders;
using Microsoft.IdentityModel.Protocols;
using Moq;
using Shouldly;
using Xunit;
@ -32,52 +30,9 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
/// </summary>
public class OpenIdConnectHandlerTests
{
static List<LogEntry> CompleteLogEntries;
static Dictionary<string, LogLevel> LogEntries;
static OpenIdConnectHandlerTests()
{
LogEntries =
new Dictionary<string, LogLevel>()
{
{ "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();
}
/// <summary>
/// Builds the complete list of log entries that are available in the runtime.
/// </summary>
private static void BuildLogEntryList()
{
CompleteLogEntries = new List<LogEntry>();
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<Claim> { new Claim("iat", EpochTime.GetIntDate(DateTime.UtcNow).ToString()), new Claim("nonce", nonceForJwt) }, DateTime.UtcNow, DateTime.UtcNow + TimeSpan.FromDays(1));
private const string ExpectedStateParameter = "expectedState";
/// <summary>
/// Sanity check that logging is filtering, hi / low water marks are checked
@ -85,7 +40,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
[Fact]
public void LoggingLevel()
{
var logger = new CustomLogger(LogLevel.Debug);
var logger = new InMemoryLogger(LogLevel.Debug);
logger.IsEnabled(LogLevel.Critical).ShouldBe<bool>(true);
logger.IsEnabled(LogLevel.Debug).ShouldBe<bool>(true);
logger.IsEnabled(LogLevel.Error).ShouldBe<bool>(true);
@ -93,7 +48,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
logger.IsEnabled(LogLevel.Verbose).ShouldBe<bool>(true);
logger.IsEnabled(LogLevel.Warning).ShouldBe<bool>(true);
logger = new CustomLogger(LogLevel.Critical);
logger = new InMemoryLogger(LogLevel.Critical);
logger.IsEnabled(LogLevel.Critical).ShouldBe<bool>(true);
logger.IsEnabled(LogLevel.Debug).ShouldBe<bool>(false);
logger.IsEnabled(LogLevel.Error).ShouldBe<bool>(false);
@ -102,208 +57,190 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
logger.IsEnabled(LogLevel.Warning).ShouldBe<bool>(false);
}
/// <summary>
/// Test <see cref="OpenIdConnectAuthenticationHandler.AuthenticateCoreAsync"/> produces expected logs.
/// Each call to 'RunVariation' is configured with an <see cref="OpenIdConnectAuthenticationOptions"/> and <see cref="OpenIdConnectMessage"/>.
/// The list of expected log entries is checked and any errors reported.
/// <see cref="CustomLoggerFactory"/> captures the logs so they can be prepared.
/// </summary>
/// <returns></returns>
[Fact]
public async Task AuthenticateCore()
[Theory, MemberData("AuthenticateCoreStateDataSet")]
public async Task AuthenticateCoreState(Action<OpenIdConnectAuthenticationOptions> 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<string, List<Tuple<LogEntry, LogEntry>>>();
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();
var server = CreateServer(new ConfigureOptions<OpenIdConnectAuthenticationOptions>(action), UrlEncoder.Default, handler);
await server.CreateClient().PostAsync("http://localhost", new FormUrlEncodedContent(message.Parameters.Where(pair => pair.Value != null)));
}
/// <summary>
/// Tests that <see cref="OpenIdConnectAuthenticationHandler"/> processes a messaage as expected.
/// The test runs two independant paths: Using <see cref="ConfigureOptions{TOptions}"/> and <see cref="IOptions{TOptions}"/>
/// </summary>
/// <param name="logLevel"><see cref="LogLevel"/> for this variation</param>
/// <param name="message">the <see cref="OpenIdConnectMessage"/> that has arrived</param>
/// <param name="action">the <see cref="OpenIdConnectAuthenticationOptions"/> delegate used for setting the options.</param>
/// <param name="errors">container for propogation of errors.</param>
/// <param name="logsEntriesExpected">the expected log entries</param>
/// <returns>a Task</returns>
private async Task RunVariation(LogLevel logLevel, OpenIdConnectMessage message, Action<OpenIdConnectAuthenticationOptions> action, Dictionary<string, List<Tuple<LogEntry, LogEntry>>> errors, int[] logsEntriesExpected)
public static TheoryData<Action<OpenIdConnectAuthenticationOptions>, 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);
}
/// <summary>
/// Populates a list of expected log entries for a test variation.
/// </summary>
/// <param name="items">the index for the <see cref="LogEntry"/> in CompleteLogEntries of interest.</param>
/// <returns>a <see cref="List{LogEntry}"/> that represents the expected entries for a test variation.</returns>
private List<LogEntry> PopulateLogEntries(int[] items)
{
var entries = new List<LogEntry>();
foreach(var item in items)
get
{
entries.Add(CompleteLogEntries[item]);
}
var formater = new AuthenticationPropertiesFormaterKeyValue();
var properties = new AuthenticationProperties();
var dataset = new TheoryData<Action<OpenIdConnectAuthenticationOptions>, OpenIdConnectMessage>();
return entries;
}
// 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);
private void DisplayLogs(List<LogEntry> logs)
{
foreach (var logentry in logs)
{
Console.WriteLine(logentry.ToString());
// 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;
}
}
private void DisplayErrors(Dictionary<string, List<Tuple<LogEntry, LogEntry>>> errors)
// Setup a notification to check for expected state.
// The state gets set by the runtime after the 'MessageReceivedNotification'
private static void SetStateOptions(OpenIdConnectAuthenticationOptions options)
{
if (errors.Count > 0)
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
options.ClientId = Guid.NewGuid().ToString();
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
options.Notifications = new OpenIdConnectAuthenticationNotifications
{
foreach (var error in errors)
AuthorizationCodeReceived = notification =>
{
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);
if (notification.ProtocolMessage.State == null && !notification.ProtocolMessage.Parameters.ContainsKey(ExpectedStateParameter))
return Task.FromResult<object>(null);
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<object>(null);
}
}
};
}
/// <summary>
/// Adds to errors if a variation if any are found.
/// </summary>
/// <param name="variation">if this has been seen before, errors will be appended, test results are easier to understand if this is unique.</param>
/// <param name="capturedLogs">these are the logs the runtime generated</param>
/// <param name="expectedLogs">these are the errors that were expected</param>
/// <param name="errors">the dictionary to record any errors</param>
private void CheckLogs(string variation, List<LogEntry> capturedLogs, List<LogEntry> expectedLogs, Dictionary<string, List<Tuple<LogEntry, LogEntry>>> errors)
[Theory, MemberData("AuthenticateCoreDataSet")]
public async Task AuthenticateCore(LogLevel logLevel, int[] expectedLogIndexes, Action<OpenIdConnectAuthenticationOptions> action, OpenIdConnectMessage message)
{
var localErrors = new List<Tuple<LogEntry, LogEntry>>();
var errors = new List<Tuple<LogEntry, LogEntry>>();
var expectedLogs = LoggingUtilities.PopulateLogEntries(expectedLogIndexes);
var handler = new OpenIdConnectAuthenticationHandlerForTestingAuthenticate();
var loggerFactory = new InMemoryLoggerFactory(logLevel);
var server = CreateServer(new ConfigureOptions<OpenIdConnectAuthenticationOptions>(action), UrlEncoder.Default, loggerFactory, handler);
if (capturedLogs.Count >= expectedLogs.Count)
{
for (int i = 0; i < capturedLogs.Count; i++)
{
if (i + 1 > expectedLogs.Count)
{
localErrors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], null));
}
else
{
if (!TestUtilities.AreEqual<LogEntry>(capturedLogs[i], expectedLogs[i]))
{
localErrors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], expectedLogs[i]));
}
}
}
}
else
{
for (int i = 0; i < expectedLogs.Count; i++)
{
if (i + 1 > capturedLogs.Count)
{
localErrors.Add(new Tuple<LogEntry, LogEntry>(null, expectedLogs[i]));
}
else
{
if (!TestUtilities.AreEqual<LogEntry>(expectedLogs[i], capturedLogs[i]))
{
localErrors.Add(new Tuple<LogEntry, LogEntry>(capturedLogs[i], expectedLogs[i]));
}
}
}
}
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));
}
if (localErrors.Count != 0)
public static TheoryData<LogLevel, int[], Action<OpenIdConnectAuthenticationOptions>, OpenIdConnectMessage> AuthenticateCoreDataSet
{
get
{
if (errors.ContainsKey(variation))
{
foreach (var error in localErrors)
{
errors[variation].Add(error);
}
}
else
{
errors[variation] = localErrors;
}
var formater = new AuthenticationPropertiesFormaterKeyValue();
var dataset = new TheoryData<LogLevel, int[], Action< OpenIdConnectAuthenticationOptions >, 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;
}
}
#region Configure Options
#region Configure Options for AuthenticateCore variations
private static void CodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
private static void DefaultOptions(OpenIdConnectAuthenticationOptions options)
{
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
options.SignInScheme = "OpenIdConnectHandlerTest";
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
options.ClientId = Guid.NewGuid().ToString();
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
}
private static void AuthorizationCodeReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.Notifications =
@ -317,7 +254,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static void CodeReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
private static void AuthorizationCodeReceivedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.Notifications =
@ -331,11 +268,32 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static void DefaultOptions(OpenIdConnectAuthenticationOptions options)
private static void AuthenticationErrorHandledOptions(OpenIdConnectAuthenticationOptions options)
{
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
options.ConfigurationManager = ConfigurationManager.DefaultStaticConfigurationManager;
options.StateDataFormat = new AuthenticationPropertiesFormater();
DefaultOptions(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (notification) =>
{
notification.HandleResponse();
return Task.FromResult<object>(null);
}
};
}
private static void AuthenticationErrorSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = (notification) =>
{
notification.SkipToNextMiddleware();
return Task.FromResult<object>(null);
}
};
}
private static void MessageReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
@ -366,6 +324,11 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static void MessageWithErrorOptions(OpenIdConnectAuthenticationOptions options)
{
AuthenticationErrorHandledOptions(options);
}
private static void SecurityTokenReceivedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
@ -394,9 +357,40 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
private static void SecurityTokenValidatedHandledOptions(OpenIdConnectAuthenticationOptions options)
private static void SecurityTokenValidatorCannotReadToken(OpenIdConnectAuthenticationOptions options)
{
AuthenticationErrorHandledOptions(options);
var mockValidator = new Mock<ISecurityTokenValidator>();
SecurityToken jwt = null;
mockValidator.Setup(v => v.ValidateToken(It.IsAny<string>(), It.IsAny<TokenValidationParameters>(), out jwt)).Returns(new ClaimsPrincipal());
mockValidator.Setup(v => v.CanReadToken(It.IsAny<string>())).Returns(false);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { mockValidator.Object };
}
private static void SecurityTokenValidatorThrows(OpenIdConnectAuthenticationOptions options)
{
AuthenticationErrorHandledOptions(options);
var mockValidator = new Mock<ISecurityTokenValidator>();
SecurityToken jwt = null;
mockValidator.Setup(v => v.ValidateToken(It.IsAny<string>(), It.IsAny<TokenValidationParameters>(), out jwt)).Throws<SecurityTokenSignatureKeyNotFoundException>();
mockValidator.Setup(v => v.CanReadToken(It.IsAny<string>())).Returns(true);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { mockValidator.Object };
}
private static void SecurityTokenValidatorValidatesAllTokens(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
var mockValidator = new Mock<ISecurityTokenValidator>();
mockValidator.Setup(v => v.ValidateToken(It.IsAny<string>(), It.IsAny<TokenValidationParameters>(), out specCompliantJwt)).Returns(new ClaimsPrincipal());
mockValidator.Setup(v => v.CanReadToken(It.IsAny<string>())).Returns(true);
options.SecurityTokenValidators = new Collection<ISecurityTokenValidator> { mockValidator.Object };
options.ProtocolValidator.RequireTimeStampInNonce = false;
options.ProtocolValidator.RequireNonce = false;
}
private static void SecurityTokenValidatedHandledOptions(OpenIdConnectAuthenticationOptions options)
{
SecurityTokenValidatorValidatesAllTokens(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -410,7 +404,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
private static void SecurityTokenValidatedSkippedOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
SecurityTokenValidatorValidatesAllTokens(options);
options.Notifications =
new OpenIdConnectAuthenticationNotifications
{
@ -422,14 +416,31 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
};
}
#endregion
private static void StateNullOptions(OpenIdConnectAuthenticationOptions options)
{
DefaultOptions(options);
}
private static TestServer CreateServer(IOptions<OpenIdConnectAuthenticationOptions> options, ILoggerFactory loggerFactory)
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(ConfigureOptions<OpenIdConnectAuthenticationOptions> options, IUrlEncoder encoder, OpenIdConnectAuthenticationHandler handler = null)
{
return TestServer.Create(
app =>
{
app.UseCustomOpenIdConnectAuthentication(options, loggerFactory);
app.UseMiddleware<OpenIdConnectAuthenticationMiddlewareForTestingAuthenticate>(options, encoder, handler);
app.Use(async (context, next) =>
{
await next();
@ -437,19 +448,18 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
},
services =>
{
services.AddAuthentication();
services.AddWebEncoders();
services.AddDataProtection();
}
);
}
private static TestServer CreateServer(CustomConfigureOptions configureOptions, ILoggerFactory loggerFactory)
private static TestServer CreateServer(ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions, IUrlEncoder encoder, ILoggerFactory loggerFactory, OpenIdConnectAuthenticationHandler handler = null)
{
return TestServer.Create(
app =>
{
app.UseCustomOpenIdConnectAuthentication(configureOptions, loggerFactory);
app.UseMiddleware<OpenIdConnectAuthenticationMiddlewareForTestingAuthenticate>(configureOptions, encoder, loggerFactory, handler);
app.Use(async (context, next) =>
{
await next();
@ -457,274 +467,10 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
},
services =>
{
services.AddAuthentication();
services.AddWebEncoders();
services.AddDataProtection();
}
);
}
}
/// <summary>
/// Extension specifies <see cref="CustomOpenIdConnectAuthenticationMiddleware"/> as the middleware.
/// </summary>
public static class OpenIdConnectAuthenticationExtensions
{
/// <summary>
/// Adds the <see cref="OpenIdConnectAuthenticationMiddleware"/> into the ASP.NET runtime.
/// </summary>
/// <param name="app">The application builder</param>
/// <param name="customConfigureOption">Options which control the processing of the OpenIdConnect protocol and token validation.</param>
/// <param name="loggerFactory">custom loggerFactory</param>
/// <returns>The application builder</returns>
public static IApplicationBuilder UseCustomOpenIdConnectAuthentication(this IApplicationBuilder app, CustomConfigureOptions customConfigureOption, ILoggerFactory loggerFactory)
{
return app.UseMiddleware<CustomOpenIdConnectAuthenticationMiddleware>(customConfigureOption, loggerFactory);
}
/// <summary>
/// Adds the <see cref="OpenIdConnectAuthenticationMiddleware"/> into the ASP.NET runtime.
/// </summary>
/// <param name="app">The application builder</param>
/// <param name="options">Options which control the processing of the OpenIdConnect protocol and token validation.</param>
/// <param name="loggerFactory">custom loggerFactory</param>
/// <returns>The application builder</returns>
public static IApplicationBuilder UseCustomOpenIdConnectAuthentication(this IApplicationBuilder app, IOptions<OpenIdConnectAuthenticationOptions> options, ILoggerFactory loggerFactory)
{
return app.UseMiddleware<CustomOpenIdConnectAuthenticationMiddleware>(options, loggerFactory);
}
}
/// <summary>
/// Provides a Facade over IOptions
/// </summary>
public class Options : IOptions<OpenIdConnectAuthenticationOptions>
{
OpenIdConnectAuthenticationOptions _options;
public Options(Action<OpenIdConnectAuthenticationOptions> action)
{
_options = new OpenIdConnectAuthenticationOptions();
action(_options);
}
OpenIdConnectAuthenticationOptions IOptions<OpenIdConnectAuthenticationOptions>.Options
{
get
{
return _options;
}
}
/// <summary>
/// For now returns _options
/// </summary>
/// <param name="name">configuration to return</param>
/// <returns></returns>
public OpenIdConnectAuthenticationOptions GetNamedOptions(string name)
{
return _options;
}
}
public class CustomConfigureOptions : ConfigureOptions<OpenIdConnectAuthenticationOptions>
{
public CustomConfigureOptions(Action<OpenIdConnectAuthenticationOptions> action)
: base(action)
{
}
public override void Configure(OpenIdConnectAuthenticationOptions options, string name = "")
{
base.Configure(options, name);
return;
}
}
/// <summary>
/// Used to control which methods are handled
/// </summary>
public class CustomOpenIdConnectAuthenticationHandler : OpenIdConnectAuthenticationHandler
{
public async Task BaseInitializeAsyncPublic(AuthenticationOptions options, HttpContext context, ILogger logger, IUrlEncoder encoder)
{
await base.BaseInitializeAsync(options, context, logger, encoder);
}
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
var redirectToIdentityProviderNotification = new RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>(Context, Options)
{
};
await Options.Notifications.RedirectToIdentityProvider(redirectToIdentityProviderNotification);
return true;
}
}
/// <summary>
/// Used to set <see cref="CustomOpenIdConnectAuthenticationHandler"/> as the AuthenticationHandler
/// which can be configured to handle certain messages.
/// </summary>
public class CustomOpenIdConnectAuthenticationMiddleware : OpenIdConnectAuthenticationMiddleware
{
public CustomOpenIdConnectAuthenticationMiddleware(
RequestDelegate next,
IDataProtectionProvider dataProtectionProvider,
ILoggerFactory loggerFactory,
IUrlEncoder encoder,
IServiceProvider services,
IOptions<ExternalAuthenticationOptions> externalOptions,
IOptions<OpenIdConnectAuthenticationOptions> options,
ConfigureOptions<OpenIdConnectAuthenticationOptions> configureOptions = null
)
: base(next, dataProtectionProvider, loggerFactory, encoder, services, externalOptions, options, configureOptions)
{
Logger = (loggerFactory as CustomLoggerFactory).Logger;
}
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new CustomOpenIdConnectAuthenticationHandler();
}
}
public class LogEntry
{
public LogEntry() { }
public int EventId { get; set; }
public Exception Exception { get; set; }
public Func<object, Exception, string> 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<LogEntry> logEntries = new List<LogEntry>();
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<object, Exception, string> 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<LogEntry> 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; } }
}
/// <summary>
/// Processing a <see cref="OpenIdConnectMessage"/> requires 'unprotecting' the state.
/// This class side-steps that process.
/// </summary>
public class AuthenticationPropertiesFormater : ISecureDataFormat<AuthenticationProperties>
{
public string Protect(AuthenticationProperties data)
{
return "protectedData";
}
AuthenticationProperties ISecureDataFormat<AuthenticationProperties>.Unprotect(string protectedText)
{
return new AuthenticationProperties();
}
}
/// <summary>
/// Used to set up different configurations of metadata for different tests
/// </summary>
public class ConfigurationManager
{
/// <summary>
/// Simple static empty manager.
/// </summary>
static public IConfigurationManager<OpenIdConnectConfiguration> DefaultStaticConfigurationManager
{
get { return new StaticConfigurationManager<OpenIdConnectConfiguration>(new OpenIdConnectConfiguration()); }
}
}
}

View File

@ -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 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
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 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
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.");
}
[Fact]
public async Task ChallengeWillSetDefaultScope()
{
var server = CreateServer(options =>
{
options.Authority = "https://login.windows.net/common";
options.ClientId = "Test Id";
});
var transaction = await SendAsync(server, "https://example.com/challenge");
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
transaction.Response.Headers.Location.Query.ShouldContain("&scope=" + UrlEncoder.Default.UrlEncode("openid profile"));
var transaction = await SendAsync(server, DefaultHost + Challenge);
transaction.SetCookie.Single().ShouldContain(OpenIdConnectAuthenticationDefaults.CookieNoncePrefix);
}
[Fact]
public async Task ChallengeWillUseOptionsProperties()
{
var queryValues = new ExpectedQueryValues(DefaultAuthority);
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";
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 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.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
/// <summary>
/// Tests RedirectToIdentityProviderNotification replaces the OpenIdConnectMesssage correctly.
/// </summary>
/// <returns>Task</returns>
[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<OpenIdConnectMessage>();
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<object>(null);
}
};
}
/// <summary>
/// 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.
/// </summary>
/// <param name="userSetsState"></param>
/// <returns></returns>
[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 =>
{
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<object>(null);
}
};
}, null, properties);
var transaction = await SendAsync(server, DefaultHost + challenge);
transaction.Response.StatusCode.ShouldBe(HttpStatusCode.Redirect);
queryValues.State = stateDataFormat.Protect(properties);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters(new string[] { OpenIdConnectParameterNames.State }));
}
public static TheoryData<string, string> StateDataSet
{
get
{
var dataset = new TheoryData<string, string>();
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<AuthenticationProperties> 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<object>(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<object>(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<string> parameters, ExpectedQueryValues queryValues, ISecureDataFormat<AuthenticationProperties> 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<string> DefaultParameters(string[] additionalParams = null)
{
var parameters =
new List<string>
{
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 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
[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,30 +306,7 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
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<OpenIdConnectAuthenticationOptions> configureOptions, Func<HttpContext, Task> handler = null)
private static TestServer CreateServer(Action<OpenIdConnectAuthenticationOptions> configureOptions, Func<HttpContext, Task> handler = null, AuthenticationProperties properties = null)
{
return TestServer.Create(app =>
{
@ -203,15 +319,25 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
var req = context.Request;
var res = context.Response;
if (req.Path == new PathString("/challenge"))
if (req.Path == new PathString(Challenge))
{
await context.Authentication.ChallengeAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/signin"))
else if (req.Path == new PathString(ChallengeWithProperties))
{
await context.Authentication.ChallengeAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, properties);
}
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()
await context.Authentication.SignInAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal());
}
else if (req.Path == new PathString("/signout"))
else if (req.Path == new PathString(Signout))
{
await context.Authentication.SignOutAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme);
}
@ -248,34 +374,39 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
{
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<string> SetCookie { get; set; }
public string ResponseText { get; set; }
public XElement ResponseElement { get; set; }
public string AuthenticationCookieValue
@ -294,57 +425,29 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
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<HttpRequestMessage, HttpResponseMessage> Sender { get; set; }
DateTime utcNow = DateTime.UtcNow;
protected override Task<HttpResponseMessage> 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<HttpResponseMessage>(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)
@ -373,8 +476,8 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
}
}
}
return nonceTime;
}
}
}

View File

@ -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
/// </summary>
public class TestUtilities
{
public const string DefaultHost = @"http://localhost";
public static bool AreEqual<T>(object obj1, object obj2, Func<object, object, bool> comparer = null) where T : class
{
if (obj1 == null && obj2 == null)
@ -63,11 +66,6 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
return false;
}
if (!AreEqual<Exception>(logEntry1.Exception, logEntry2.Exception))
{
return false;
}
if (logEntry1.State == null && logEntry2.State == null)
{
return true;
@ -104,5 +102,26 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect
return AreEqual<Exception>(exception1.InnerException, exception2.InnerException);
}
static public IConfigurationManager<OpenIdConnectConfiguration> DefaultOpenIdConnectConfigurationManager
{
get
{
return new StaticConfigurationManager<OpenIdConnectConfiguration>(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",
};
}
}
}
}