Update OpenId Connect Challenge Tests

1. Expand the test coverage: add tests covers events work flow.
2. Move OpenID connect challenge tests to their own class.
3. Further refactory the test settings and utilities.
This commit is contained in:
Troy Dai 2016-08-22 14:47:30 -07:00
parent 62f0f6e857
commit abc1b37ee1
12 changed files with 817 additions and 722 deletions

View File

@ -1,70 +0,0 @@
// 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 System.Text.Encodings.Web;
using Microsoft.AspNetCore.Http.Authentication;
namespace Microsoft.AspNetCore.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 sb = new StringBuilder();
foreach(var item in data.Items)
{
sb.Append(Uri.EscapeDataString(item.Key) + " " + Uri.EscapeDataString(item.Value) + " ");
}
return sb.ToString();
}
public string Protect(AuthenticationProperties data, string purpose)
{
return Protect(data);
}
public AuthenticationProperties Unprotect(string protectedText)
{
if (string.IsNullOrEmpty(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;
}
public AuthenticationProperties Unprotect(string protectedText, string purpose)
{
return Unprotect(protectedText);
}
}
}

View File

@ -1,175 +0,0 @@
// 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 System.Text.Encodings.Web;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
namespace Microsoft.AspNetCore.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 = OpenIdConnectScope.OpenIdProfile;
result.ResponseType = OpenIdConnectResponseType.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.Authentication;
public string Resource { get; set; } = Guid.NewGuid().ToString();
public string ResponseMode { get; set; } = OpenIdConnectResponseMode.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.Token)
{
return Configuration?.EndSessionEndpoint ?? Authority + @"/oauth2/token";
}
else if (RequestType == OpenIdConnectRequestType.Logout)
{
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.Encode(ClientId); }
}
public string ExpectedRedirectUri
{
get { return OpenIdConnectParameterNames.RedirectUri + "=" + Encoder.Encode(RedirectUri); }
}
public string ExpectedResource
{
get { return OpenIdConnectParameterNames.Resource + "=" + Encoder.Encode(Resource); }
}
public string ExpectedResponseMode
{
get { return OpenIdConnectParameterNames.ResponseMode + "=" + Encoder.Encode(ResponseMode); }
}
public string ExpectedScope
{
get { return OpenIdConnectParameterNames.Scope + "=" + Encoder.Encode(Scope); }
}
public string ExpectedState
{
get { return Encoder.Encode(State); }
}
}
}

View File

@ -1,10 +0,0 @@
// 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.
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect.Infrastructre
{
internal class TestDefaultValues
{
public static readonly string DefaultAuthority = @"https://example.com/common";
}
}

View File

@ -0,0 +1,21 @@
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
internal class MockOpenIdConnectMessage : OpenIdConnectMessage
{
public string TestAuthorizeEndpoint { get; set; }
public string TestLogoutRequest { get; set; }
public override string CreateAuthenticationRequestUrl()
{
return TestAuthorizeEndpoint ?? base.CreateAuthenticationRequestUrl();
}
public override string CreateLogoutRequestUrl()
{
return TestLogoutRequest ?? base.CreateLogoutRequestUrl();
}
}
}

View File

@ -0,0 +1,322 @@
// 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.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
public class OpenIdConnectChallengeTests
{
[Fact]
public async Task ChallengeIsIssuedCorrectly()
{
var settings = new TestSettings(
opt => opt.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
settings.ValidateChallengeRedirect(
res.Headers.Location,
OpenIdConnectParameterNames.ClientId,
OpenIdConnectParameterNames.ResponseType,
OpenIdConnectParameterNames.ResponseMode,
OpenIdConnectParameterNames.Scope,
OpenIdConnectParameterNames.RedirectUri);
}
/*
Example of a form post
<body>
<form name=\ "form\" method=\ "post\" action=\ "https://login.microsoftonline.com/common/oauth2/authorize\">
<input type=\ "hidden\" name=\ "client_id\" value=\ "51e38103-238f-410f-a5d5-61991b203e50\" />
<input type=\ "hidden\" name=\ "redirect_uri\" value=\ "https://example.com/signin-oidc\" />
<input type=\ "hidden\" name=\ "response_type\" value=\ "id_token\" />
<input type=\ "hidden\" name=\ "scope\" value=\ "openid profile\" />
<input type=\ "hidden\" name=\ "response_mode\" value=\ "form_post\" />
<input type=\ "hidden\" name=\ "nonce\" value=\ "636072461997914230.NTAwOGE1MjQtM2VhYS00ZDU0LWFkYzYtNmZiYWE2MDRkODg3OTlkMDFmOWUtOTMzNC00ZmI2LTg1Y2YtOWM4OTlhNjY0Yjli\" />
<input type=\ "hidden\" name=\ "state\" value=\
"CfDJ8Jh1NKaF0T5AnK4qsqzzIs89srKe4iEaBWd29MNph4Ki887QKgkD24wjhZ0ciH-ar6A_jUmRI2O5haXN2-YXbC0ZRuRAvNsx5LqbPTdh4MJBIwXWkG_rM0T0tI3h5Y2pDttWSaku6a_nzFLUYBrKfsE7sDLVoTDrzzOcHrRQhdztqOOeNUuu2wQXaKwlOtNI21ShtN9EVxvSGFOxUUOwVih4nFdF40fBcbsuPpcpCPkLARQaFRJSYsNKiP7pcFMnRwzZhnISHlyGKkzwJ1DIx7nsmdiQFBGljimw5GnYAs-5ru9L3w8NnPjkl96OyQ8MJOcayMDmOY26avs2sYP_Zw0\" />
<noscript>Click here to finish the process: <input type=\"submit\" /></noscript>
</form>
<script>
document.form.submit();
</script>
</body>
*/
[Fact]
public async Task ChallengeIssueedCorrectlyForFormPost()
{
var settings = new TestSettings(
opt => opt.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.OK, res.StatusCode);
Assert.Equal("text/html", transaction.Response.Content.Headers.ContentType.MediaType);
var body = await res.Content.ReadAsStringAsync();
settings.ValidateChallengeFormPost(
body,
OpenIdConnectParameterNames.ClientId,
OpenIdConnectParameterNames.ResponseType,
OpenIdConnectParameterNames.ResponseMode,
OpenIdConnectParameterNames.Scope,
OpenIdConnectParameterNames.RedirectUri);
}
[Theory]
[InlineData("sample_user_state")]
[InlineData(null)]
public async Task ChallengeCanSetUserStateThroughProperties(string userState)
{
var settings = new TestSettings();
var properties = new AuthenticationProperties();
properties.Items.Add(OpenIdConnectDefaults.UserstatePropertiesKey, userState);
var server = TestServerBuilder.CreateServer(settings.Options, handler: null, properties: properties);
var transaction = await TestTransaction.SendAsync(server, TestDefaultValues.TestHost + TestServerBuilder.ChallengeWithProperties);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var values = settings.ValidateChallengeRedirect(res.Headers.Location);
var actualState = values[OpenIdConnectParameterNames.State];
var actualProperties = settings.Options.StateDataFormat.Unprotect(actualState);
Assert.Equal(userState ?? string.Empty, actualProperties.Items[OpenIdConnectDefaults.UserstatePropertiesKey]);
}
[Theory]
[InlineData("sample_user_state")]
[InlineData(null)]
public async Task OnRedirectToIdentityProviderEventCanSetState(string userState)
{
var settings = new TestSettings(opt =>
{
opt.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.State = userState;
return Task.FromResult(0);
}
};
});
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
var values = settings.ValidateChallengeRedirect(res.Headers.Location);
var actualState = values[OpenIdConnectParameterNames.State];
var actualProperties = settings.Options.StateDataFormat.Unprotect(actualState);
if (userState != null)
{
Assert.Equal(userState, actualProperties.Items[OpenIdConnectDefaults.UserstatePropertiesKey]);
}
else
{
Assert.False(actualProperties.Items.ContainsKey(OpenIdConnectDefaults.UserstatePropertiesKey));
}
}
[Fact]
public async Task OnRedirectToIdentityProviderEventIsHit()
{
var eventIsHit = false;
var settings = new TestSettings(
opts =>
{
opts.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
eventIsHit = true;
return Task.FromResult(0);
}
};
}
);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
Assert.True(eventIsHit);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
settings.ValidateChallengeRedirect(
res.Headers.Location,
OpenIdConnectParameterNames.ClientId,
OpenIdConnectParameterNames.ResponseType,
OpenIdConnectParameterNames.ResponseMode,
OpenIdConnectParameterNames.Scope,
OpenIdConnectParameterNames.RedirectUri);
}
[Fact]
public async Task OnRedirectToIdentityProviderEventCanReplaceValues()
{
var newClientId = Guid.NewGuid().ToString();
var settings = new TestSettings(
opts =>
{
opts.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.ClientId = newClientId;
return Task.FromResult(0);
}
};
}
);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
settings.ValidateChallengeRedirect(
res.Headers.Location,
OpenIdConnectParameterNames.ResponseType,
OpenIdConnectParameterNames.ResponseMode,
OpenIdConnectParameterNames.Scope,
OpenIdConnectParameterNames.RedirectUri);
var actual = res.Headers.Location.Query.Trim('?').Split('&').Single(seg => seg.StartsWith($"{OpenIdConnectParameterNames.ClientId}="));
Assert.Equal($"{OpenIdConnectParameterNames.ClientId}={newClientId}", actual);
}
[Fact]
public async Task OnRedirectToIdentityProviderEventCanReplaceMessage()
{
var newMessage = new MockOpenIdConnectMessage
{
TestAuthorizeEndpoint = $"http://example.com/{Guid.NewGuid()}/oauth2/signin"
};
var settings = new TestSettings(
opts =>
{
opts.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage = newMessage;
return Task.FromResult(0);
}
};
}
);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
// The CreateAuthenticationRequestUrl method is overridden MockOpenIdConnectMessage where
// query string is not generated and the authorization endpoint is replaced.
Assert.Equal(newMessage.TestAuthorizeEndpoint, res.Headers.Location.AbsoluteUri);
}
[Fact]
public async Task OnRedirectToIdentityProviderEventHandlesResponse()
{
var settings = new TestSettings(
opts =>
{
opts.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.HandleResponse();
return Task.FromResult(0);
}
};
}
);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.OK, res.StatusCode);
Assert.Null(res.Headers.Location);
}
// This test can be further refined. When one auth middleware skips, the authentication responsibility
// will be flowed to the next one. A dummy auth middleware can be added to ensure the correct logic.
[Fact]
public async Task OnRedirectToIdentityProviderEventSkipResponse()
{
var settings = new TestSettings(
opts =>
{
opts.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.SkipToNextMiddleware();
return Task.FromResult(0);
}
};
}
);
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var res = transaction.Response;
Assert.Equal(HttpStatusCode.OK, res.StatusCode);
Assert.Null(res.Headers.Location);
}
[Fact]
public async Task ChallengeSetsNonceAndStateCookies()
{
var settings = new TestSettings();
var server = settings.CreateTestServer();
var transaction = await TestTransaction.SendAsync(server, ChallengeEndpoint);
var firstCookie = transaction.SetCookie.First();
Assert.Contains(OpenIdConnectDefaults.CookieNoncePrefix, firstCookie);
Assert.Contains("expires", firstCookie);
var secondCookie = transaction.SetCookie.Skip(1).First();
Assert.StartsWith(".AspNetCore.Correlation.OpenIdConnect.", secondCookie);
Assert.Contains("expires", secondCookie);
}
private static string ChallengeEndpoint => TestDefaultValues.TestHost + TestServerBuilder.Challenge;
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect.Infrastructre;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;

View File

@ -2,24 +2,11 @@
// 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.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect.Infrastructre;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
@ -29,318 +16,47 @@ namespace Microsoft.AspNetCore.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 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 ChallengeWillIssueHtmlFormWhenEnabled()
{
var server = CreateServer(new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = "Test Id",
Configuration = TestUtilities.DefaultOpenIdConnectConfiguration,
AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost
});
var transaction = await SendAsync(server, DefaultHost + Challenge);
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
Assert.Equal("text/html", transaction.Response.Content.Headers.ContentType.MediaType);
Assert.Contains("form", transaction.ResponseText);
}
[Fact]
public async Task ChallengeWillSetDefaults()
{
var stateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
var queryValues = ExpectedQueryValues.Defaults(TestDefaultValues.DefaultAuthority);
queryValues.State = OpenIdConnectDefaults.AuthenticationPropertiesKey + "=" + stateDataFormat.Protect(new AuthenticationProperties());
var server = CreateServer(GetOptions(DefaultParameters(), queryValues));
var transaction = await SendAsync(server, DefaultHost + Challenge);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
[Fact]
public async Task ChallengeWillSetNonceAndStateCookies()
{
var server = CreateServer(new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = "Test Id",
Configuration = TestUtilities.DefaultOpenIdConnectConfiguration
});
var transaction = await SendAsync(server, DefaultHost + Challenge);
var firstCookie = transaction.SetCookie.First();
Assert.Contains(OpenIdConnectDefaults.CookieNoncePrefix, firstCookie);
Assert.Contains("expires", firstCookie);
var secondCookie = transaction.SetCookie.Skip(1).First();
Assert.StartsWith(".AspNetCore.Correlation.OpenIdConnect.", secondCookie);
Assert.Contains("expires", secondCookie);
}
[Fact]
public async Task ChallengeWillUseOptionsProperties()
{
var queryValues = new ExpectedQueryValues(TestDefaultValues.DefaultAuthority);
var server = CreateServer(GetOptions(DefaultParameters(), queryValues));
var transaction = await SendAsync(server, DefaultHost + Challenge);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
/// <summary>
/// Tests RedirectForAuthenticationContext replaces the OpenIdConnectMesssage correctly.
/// </summary>
/// <returns>Task</returns>
[Fact]
public async Task ChallengeSettingMessage()
{
var configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = ExpectedAuthorizeRequest,
};
var queryValues = new ExpectedQueryValues(TestDefaultValues.DefaultAuthority, configuration)
{
RequestType = OpenIdConnectRequestType.Authentication
};
var server = CreateServer(GetProtocolMessageOptions());
var transaction = await SendAsync(server, DefaultHost + Challenge);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, new string[] { });
}
/// <summary>
/// Tests RedirectForSignOutContext replaces the OpenIdConnectMesssage correctly.
/// </summary>
/// summary>
/// <returns>Task</returns>
[Fact]
public async Task SignOutSettingMessage()
{
var configuration = new OpenIdConnectConfiguration
var setting = new TestSettings(opt =>
{
EndSessionEndpoint = ExpectedLogoutRequest
};
var queryValues = new ExpectedQueryValues(TestDefaultValues.DefaultAuthority, configuration)
{
RequestType = OpenIdConnectRequestType.Logout
};
var server = CreateServer(GetProtocolMessageOptions());
var transaction = await SendAsync(server, DefaultHost + Signout);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, new string[] { });
}
private static OpenIdConnectOptions GetProtocolMessageOptions()
{
var options = new OpenIdConnectOptions();
var fakeOpenIdRequestMessage = new FakeOpenIdConnectMessage(ExpectedAuthorizeRequest, ExpectedLogoutRequest);
options.AutomaticChallenge = true;
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = (context) =>
opt.Configuration = new OpenIdConnectConfiguration
{
context.ProtocolMessage = fakeOpenIdRequestMessage;
return Task.FromResult(0);
},
OnRedirectToIdentityProviderForSignOut = (context) =>
{
context.ProtocolMessage = fakeOpenIdRequestMessage;
return Task.FromResult(0);
}
};
options.ClientId = "Test Id";
options.Configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
return options;
}
private class FakeOpenIdConnectMessage : OpenIdConnectMessage
{
private readonly string _authorizeRequest;
private readonly string _logoutRequest;
public FakeOpenIdConnectMessage(string authorizeRequest, string logoutRequest)
{
_authorizeRequest = authorizeRequest;
_logoutRequest = logoutRequest;
}
public override string CreateAuthenticationRequestUrl()
{
return _authorizeRequest;
}
public override string CreateLogoutRequestUrl()
{
return _logoutRequest;
}
}
/// <summary>
/// Tests for users who want to add 'state'. There are two ways to do it.
/// 1. Users set 'state' (OpenIdConnectMessage.State) in the event. The runtime appends to that state.
/// 2. Users add to the AuthenticationProperties (context.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(TestDefaultValues.DefaultAuthority);
var stateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
var properties = new AuthenticationProperties();
if (challenge == ChallengeWithProperties)
{
properties.Items.Add("item1", Guid.NewGuid().ToString());
}
var options = GetOptions(DefaultParameters(new string[] { OpenIdConnectParameterNames.State }), queryValues, stateDataFormat);
options.AutomaticChallenge = challenge.Equals(ChallengeWithOutContext);
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.State = userState;
context.ProtocolMessage.RedirectUri = queryValues.RedirectUri;
return Task.FromResult<object>(null);
}
};
var server = CreateServer(options, null, properties);
var transaction = await SendAsync(server, DefaultHost + challenge);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
if (challenge != ChallengeWithProperties)
{
if (userState != null)
{
properties.Items.Add(OpenIdConnectDefaults.UserstatePropertiesKey, userState);
}
properties.Items.Add(OpenIdConnectDefaults.RedirectUriForCodePropertiesKey, queryValues.RedirectUri);
}
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 ChallengeWillUseEvents()
{
var queryValues = new ExpectedQueryValues(TestDefaultValues.DefaultAuthority);
var queryValuesSetInEvent = new ExpectedQueryValues(TestDefaultValues.DefaultAuthority);
var options = GetOptions(DefaultParameters(), queryValues);
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.ClientId = queryValuesSetInEvent.ClientId;
context.ProtocolMessage.RedirectUri = queryValuesSetInEvent.RedirectUri;
context.ProtocolMessage.Resource = queryValuesSetInEvent.Resource;
context.ProtocolMessage.Scope = queryValuesSetInEvent.Scope;
return Task.FromResult<object>(null);
}
};
var server = CreateServer(options);
var transaction = await SendAsync(server, DefaultHost + Challenge);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
queryValuesSetInEvent.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, DefaultParameters());
}
private OpenIdConnectOptions GetOptions(List<string> parameters, ExpectedQueryValues queryValues, ISecureDataFormat<AuthenticationProperties> secureDataFormat = null)
{
var options = new OpenIdConnectOptions();
foreach (var param in parameters)
{
if (param.Equals(OpenIdConnectParameterNames.ClientId))
options.ClientId = queryValues.ClientId;
else if (param.Equals(OpenIdConnectParameterNames.Resource))
options.Resource = queryValues.Resource;
else if (param.Equals(OpenIdConnectParameterNames.Scope))
{
options.Scope.Clear();
foreach (var scope in queryValues.Scope.Split(' '))
{
options.Scope.Add(scope);
}
}
}
options.Authority = queryValues.Authority;
options.Configuration = queryValues.Configuration;
options.StateDataFormat = secureDataFormat ?? new AuthenticationPropertiesFormaterKeyValue();
return options;
}
private List<string> DefaultParameters(string[] additionalParams = null)
{
var parameters =
new List<string>
{
OpenIdConnectParameterNames.ClientId,
OpenIdConnectParameterNames.Resource,
OpenIdConnectParameterNames.ResponseMode,
OpenIdConnectParameterNames.Scope,
EndSessionEndpoint = "https://example.com/signout_test/signout_request"
};
});
if (additionalParams != null)
parameters.AddRange(additionalParams);
var server = setting.CreateTestServer();
return parameters;
}
var transaction = await TestTransaction.SendAsync(server, DefaultHost + TestServerBuilder.Signout);
var res = transaction.Response;
private static void DefaultChallengeOptions(OpenIdConnectOptions options)
{
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
options.AutomaticChallenge = true;
options.ClientId = Guid.NewGuid().ToString();
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
Assert.NotNull(res.Headers.Location);
setting.ValidateSignoutRedirect(transaction.Response.Headers.Location);
}
[Fact]
public async Task SignOutWithDefaultRedirectUri()
{
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(new OpenIdConnectOptions
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = "Test Id",
Configuration = configuration
});
var transaction = await SendAsync(server, DefaultHost + Signout);
var transaction = await TestTransaction.SendAsync(server, DefaultHost + TestServerBuilder.Signout);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Equal(configuration.EndSessionEndpoint, transaction.Response.Headers.Location.AbsoluteUri);
}
@ -348,8 +64,8 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
[Fact]
public async Task SignOutWithCustomRedirectUri()
{
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(new OpenIdConnectOptions
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = "Test Id",
@ -357,7 +73,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
PostLogoutRedirectUri = "https://example.com/logout"
});
var transaction = await SendAsync(server, DefaultHost + Signout);
var transaction = await TestTransaction.SendAsync(server, DefaultHost + TestServerBuilder.Signout);
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Contains(UrlEncoder.Default.Encode("https://example.com/logout"), transaction.Response.Headers.Location.AbsoluteUri);
}
@ -365,8 +81,8 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
[Fact]
public async Task SignOutWith_Specific_RedirectUri_From_Authentication_Properites()
{
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
var server = CreateServer(new OpenIdConnectOptions
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = "Test Id",
@ -374,136 +90,13 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
PostLogoutRedirectUri = "https://example.com/logout"
});
var transaction = await SendAsync(server, "https://example.com/signout_with_specific_redirect_uri");
var transaction = await TestTransaction.SendAsync(server, "https://example.com/signout_with_specific_redirect_uri");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Contains(UrlEncoder.Default.Encode("http://www.example.com/specific_redirect_uri"), transaction.Response.Headers.Location.AbsoluteUri);
}
private static TestServer CreateServer(OpenIdConnectOptions options, Func<HttpContext, Task> handler = null, AuthenticationProperties properties = null)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme
});
app.UseOpenIdConnectAuthentication(options);
app.Use(async (context, next) =>
{
var req = context.Request;
var res = context.Response;
if (req.Path == new PathString(Challenge))
{
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString(ChallengeWithProperties))
{
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.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(OpenIdConnectDefaults.AuthenticationScheme, new ClaimsPrincipal());
}
else if (req.Path == new PathString(Signout))
{
await context.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/signout_with_specific_redirect_uri"))
{
await context.Authentication.SignOutAsync(
OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties() { RedirectUri = "http://www.example.com/specific_redirect_uri" });
}
else if (handler != null)
{
await handler(context);
}
else
{
await next();
}
});
})
.ConfigureServices(services =>
{
services.AddAuthentication();
services.Configure<SharedAuthenticationOptions>(authOptions =>
{
authOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
});
return new TestServer(builder);
}
private static async Task<Transaction> SendAsync(TestServer server, string uri, string cookieHeader = null)
{
var request = new HttpRequestMessage(HttpMethod.Get, uri);
if (!string.IsNullOrEmpty(cookieHeader))
{
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();
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
{
get
{
if (SetCookie != null && SetCookie.Count > 0)
{
var authCookie = SetCookie.SingleOrDefault(c => c.Contains(".AspNetCore.Cookie="));
if (authCookie != null)
{
return authCookie.Substring(0, authCookie.IndexOf(';'));
}
}
return null;
}
}
}
[Fact]
// Test Cases for calculating the expiration time of cookie from cookie name
[Fact]
public void NonceCookieExpirationTime()
{
DateTime utcNow = DateTime.UtcNow;

View File

@ -0,0 +1,48 @@
// 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.AspNetCore.Builder;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
internal class TestDefaultValues
{
public static readonly string DefaultAuthority = @"https://login.microsoftonline.com/common";
public static readonly string TestHost = @"https://example.com";
public static OpenIdConnectOptions CreateOpenIdConnectOptions() =>
new OpenIdConnectOptions
{
Authority = TestDefaultValues.DefaultAuthority,
ClientId = Guid.NewGuid().ToString(),
Configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration()
};
public static OpenIdConnectOptions CreateOpenIdConnectOptions(Action<OpenIdConnectOptions> update)
{
var options = CreateOpenIdConnectOptions();
if (update != null)
{
update(options);
}
return options;
}
public static OpenIdConnectConfiguration CreateDefaultOpenIdConnectConfiguration() =>
new OpenIdConnectConfiguration()
{
AuthorizationEndpoint = DefaultAuthority + "/oauth2/authorize",
EndSessionEndpoint = DefaultAuthority + "/oauth2/endsessionendpoint",
TokenEndpoint = DefaultAuthority + "/oauth2/token"
};
public static IConfigurationManager<OpenIdConnectConfiguration> CreateDefaultOpenIdConnectConfigurationManager() =>
new StaticConfigurationManager<OpenIdConnectConfiguration>(CreateDefaultOpenIdConnectConfiguration());
}
}

View File

@ -0,0 +1,97 @@
// 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.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
internal class TestServerBuilder
{
public static readonly string Challenge = "/challenge";
public static readonly string ChallengeWithOutContext = "/challengeWithOutContext";
public static readonly string ChallengeWithProperties = "/challengeWithProperties";
public static readonly string Signin = "/signin";
public static readonly string Signout = "/signout";
public static TestServer CreateServer(OpenIdConnectOptions options)
{
return CreateServer(options, handler: null, properties: null);
}
public static TestServer CreateServer(
OpenIdConnectOptions options,
Func<HttpContext, Task> handler,
AuthenticationProperties properties)
{
var builder = new WebHostBuilder()
.Configure(app =>
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme
});
app.UseOpenIdConnectAuthentication(options);
app.Use(async (context, next) =>
{
var req = context.Request;
var res = context.Response;
if (req.Path == new PathString(Challenge))
{
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString(ChallengeWithProperties))
{
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.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(OpenIdConnectDefaults.AuthenticationScheme, new ClaimsPrincipal());
}
else if (req.Path == new PathString(Signout))
{
await context.Authentication.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
else if (req.Path == new PathString("/signout_with_specific_redirect_uri"))
{
await context.Authentication.SignOutAsync(
OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties() { RedirectUri = "http://www.example.com/specific_redirect_uri" });
}
else if (handler != null)
{
await handler(context);
}
else
{
await next();
}
});
})
.ConfigureServices(services =>
{
services.AddAuthentication();
services.Configure<SharedAuthenticationOptions>(authOptions => authOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
});
return new TestServer(builder);
}
}
}

View File

@ -0,0 +1,230 @@
// 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.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Xml.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Xunit;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
/// <summary>
/// This helper class is used to check that query string parameters are as expected.
/// </summary>
internal class TestSettings
{
private readonly OpenIdConnectOptions _options;
public TestSettings() : this(configure: null)
{
}
public TestSettings(Action<OpenIdConnectOptions> configure)
{
_options = TestDefaultValues.CreateOpenIdConnectOptions(configure);
}
public TestSettings(OpenIdConnectOptions options)
{
_options = options;
}
public OpenIdConnectOptions Options => _options;
public UrlEncoder Encoder => UrlEncoder.Default;
public string ExpectedState { get; set; }
public TestServer CreateTestServer() => TestServerBuilder.CreateServer(Options);
public IDictionary<string, string> ValidateChallengeFormPost(string responseBody, params string[] parametersToValidate)
{
IDictionary<string, string> formInputs = null;
var errors = new List<string>();
var xdoc = XDocument.Parse(responseBody.Replace("doctype", "DOCTYPE"));
var forms = xdoc.Descendants("form");
if (forms.Count() != 1)
{
errors.Add("Only one form element is expected in response body.");
}
else
{
formInputs = forms.Single()
.Elements("input")
.ToDictionary(elem => elem.Attribute("name").Value,
elem => elem.Attribute("value").Value);
ValidateParameters(formInputs, parametersToValidate, errors, htmlEncoded: false);
}
if (errors.Any())
{
var buf = new StringBuilder();
buf.AppendLine($"The challenge form post is not valid.");
// buf.AppendLine();
foreach (var error in errors)
{
buf.AppendLine(error);
}
Debug.WriteLine(buf.ToString());
Assert.True(false, buf.ToString());
}
return formInputs;
}
public IDictionary<string, string> ValidateChallengeRedirect(Uri redirectUri, params string[] parametersToValidate) =>
ValidateRedirectCore(redirectUri, OpenIdConnectRequestType.Authentication, parametersToValidate);
public IDictionary<string, string> ValidateSignoutRedirect(Uri redirectUri, params string[] parametersToValidate) =>
ValidateRedirectCore(redirectUri, OpenIdConnectRequestType.Logout, parametersToValidate);
private IDictionary<string, string> ValidateRedirectCore(Uri redirectUri, OpenIdConnectRequestType requestType, string[] parametersToValidate)
{
var errors = new List<string>();
// Validate the authority
ValidateExpectedAuthority(redirectUri.AbsoluteUri, errors, requestType);
// Convert query to dictionary
var queryDict = string.IsNullOrEmpty(redirectUri.Query) ?
new Dictionary<string, string>() :
redirectUri.Query.TrimStart('?').Split('&').Select(part => part.Split('=')).ToDictionary(parts => parts[0], parts => parts[1]);
// Validate the query string parameters
ValidateParameters(queryDict, parametersToValidate, errors, htmlEncoded: true);
if (errors.Any())
{
var buf = new StringBuilder();
buf.AppendLine($"The redirect uri is not valid.");
buf.AppendLine(redirectUri.AbsoluteUri);
foreach (var error in errors)
{
buf.AppendLine(error);
}
Debug.WriteLine(buf.ToString());
Assert.True(false, buf.ToString());
}
return queryDict;
}
private void ValidateParameters(
IDictionary<string, string> actualValues,
IEnumerable<string> parametersToValidate,
ICollection<string> errors,
bool htmlEncoded)
{
foreach (var paramToValidate in parametersToValidate)
{
switch (paramToValidate)
{
case OpenIdConnectParameterNames.ClientId:
ValidateClientId(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.ResponseType:
ValidateResponseType(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.ResponseMode:
ValidateResponseMode(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.Scope:
ValidateScope(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.RedirectUri:
ValidateRedirectUri(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.Resource:
ValidateResource(actualValues, errors, htmlEncoded);
break;
case OpenIdConnectParameterNames.State:
ValidateState(actualValues, errors, htmlEncoded);
break;
default:
throw new InvalidOperationException($"Unknown parameter \"{paramToValidate}\".");
}
}
}
private void ValidateExpectedAuthority(string absoluteUri, ICollection<string> errors, OpenIdConnectRequestType requestType)
{
string expectedAuthority;
switch (requestType)
{
case OpenIdConnectRequestType.Token:
expectedAuthority = _options.Configuration?.TokenEndpoint ?? _options.Authority + @"/oauth2/token";
break;
case OpenIdConnectRequestType.Logout:
expectedAuthority = _options.Configuration?.EndSessionEndpoint ?? _options.Authority + @"/oauth2/logout";
break;
default:
expectedAuthority = _options.Configuration?.AuthorizationEndpoint ?? _options.Authority + @"/oauth2/authorize";
break;
}
if (!absoluteUri.StartsWith(expectedAuthority))
{
errors.Add($"ExpectedAuthority: {expectedAuthority}");
}
}
private void ValidateClientId(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.ClientId, _options.ClientId, actualQuery, errors, htmlEncoded);
private void ValidateResponseType(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.ResponseType, _options.ResponseType, actualQuery, errors, htmlEncoded);
private void ValidateResponseMode(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.ResponseMode, _options.ResponseMode, actualQuery, errors, htmlEncoded);
private void ValidateScope(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.Scope, string.Join(" ", _options.Scope), actualQuery, errors, htmlEncoded);
private void ValidateRedirectUri(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.RedirectUri, TestDefaultValues.TestHost + _options.CallbackPath, actualQuery, errors, htmlEncoded);
private void ValidateResource(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.RedirectUri, _options.Resource, actualQuery, errors, htmlEncoded);
private void ValidateState(IDictionary<string, string> actualQuery, ICollection<string> errors, bool htmlEncoded) =>
ValidateQueryParameter(OpenIdConnectParameterNames.State, ExpectedState, actualQuery, errors, htmlEncoded);
private void ValidateQueryParameter(
string parameterName,
string expectedValue,
IDictionary<string, string> actualQuery,
ICollection<string> errors,
bool htmlEncoded)
{
string actualValue;
if (actualQuery.TryGetValue(parameterName, out actualValue))
{
if (htmlEncoded)
{
expectedValue = Encoder.Encode(expectedValue);
}
if (actualValue != expectedValue)
{
errors.Add($"Query parameter {parameterName}'s expected value is {expectedValue} but its actual value is {actualValue}");
}
}
else
{
errors.Add($"Query parameter {parameterName} is missing");
}
}
}
}

View File

@ -0,0 +1,77 @@
// 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.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.AspNetCore.TestHost;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
internal class TestTransaction
{
public static Task<TestTransaction> SendAsync(TestServer server, string url)
{
return SendAsync(server, url, cookieHeader: null);
}
public static async Task<TestTransaction> SendAsync(TestServer server, string uri, string cookieHeader)
{
var request = new HttpRequestMessage(HttpMethod.Get, uri);
if (!string.IsNullOrEmpty(cookieHeader))
{
request.Headers.Add("Cookie", cookieHeader);
}
var transaction = new TestTransaction
{
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();
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;
}
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
{
get
{
if (SetCookie != null && SetCookie.Count > 0)
{
var authCookie = SetCookie.SingleOrDefault(c => c.Contains(".AspNetCore.Cookie="));
if (authCookie != null)
{
return authCookie.Substring(0, authCookie.IndexOf(';'));
}
}
return null;
}
}
}
}

View File

@ -1,37 +0,0 @@
// 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 Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
{
/// <summary>
/// These utilities are designed to test openidconnect related flows
/// </summary>
public class TestUtilities
{
public const string DefaultHost = @"http://localhost";
public static IConfigurationManager<OpenIdConnectConfiguration> DefaultOpenIdConnectConfigurationManager
{
get
{
return new StaticConfigurationManager<OpenIdConnectConfiguration>(DefaultOpenIdConnectConfiguration);
}
}
public static OpenIdConnectConfiguration DefaultOpenIdConnectConfiguration
{
get
{
return new OpenIdConnectConfiguration()
{
AuthorizationEndpoint = @"https://login.microsoftonline.com/common/oauth2/authorize",
EndSessionEndpoint = @"https://login.microsoftonline.com/common/oauth2/endsessionendpoint",
TokenEndpoint = @"https://login.microsoftonline.com/common/oauth2/token",
};
}
}
}
}