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:
parent
62f0f6e857
commit
abc1b37ee1
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect.Infrastructre;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.TestHost;
|
using Microsoft.AspNetCore.TestHost;
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,11 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Threading.Tasks;
|
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.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 Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -29,318 +16,47 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
|
||||||
{
|
{
|
||||||
static string noncePrefix = "OpenIdConnect." + "Nonce.";
|
static string noncePrefix = "OpenIdConnect." + "Nonce.";
|
||||||
static string nonceDelimiter = ".";
|
static string nonceDelimiter = ".";
|
||||||
const string Challenge = "/challenge";
|
|
||||||
const string ChallengeWithOutContext = "/challengeWithOutContext";
|
|
||||||
const string ChallengeWithProperties = "/challengeWithProperties";
|
|
||||||
const string DefaultHost = @"https://example.com";
|
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 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>
|
/// <summary>
|
||||||
/// Tests RedirectForSignOutContext replaces the OpenIdConnectMesssage correctly.
|
/// Tests RedirectForSignOutContext replaces the OpenIdConnectMesssage correctly.
|
||||||
/// </summary>
|
/// summary>
|
||||||
/// <returns>Task</returns>
|
/// <returns>Task</returns>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SignOutSettingMessage()
|
public async Task SignOutSettingMessage()
|
||||||
{
|
{
|
||||||
var configuration = new OpenIdConnectConfiguration
|
var setting = new TestSettings(opt =>
|
||||||
{
|
{
|
||||||
EndSessionEndpoint = ExpectedLogoutRequest
|
opt.Configuration = new OpenIdConnectConfiguration
|
||||||
};
|
|
||||||
|
|
||||||
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) =>
|
|
||||||
{
|
{
|
||||||
context.ProtocolMessage = fakeOpenIdRequestMessage;
|
EndSessionEndpoint = "https://example.com/signout_test/signout_request"
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (additionalParams != null)
|
var server = setting.CreateTestServer();
|
||||||
parameters.AddRange(additionalParams);
|
|
||||||
|
|
||||||
return parameters;
|
var transaction = await TestTransaction.SendAsync(server, DefaultHost + TestServerBuilder.Signout);
|
||||||
}
|
var res = transaction.Response;
|
||||||
|
|
||||||
private static void DefaultChallengeOptions(OpenIdConnectOptions options)
|
Assert.Equal(HttpStatusCode.Redirect, res.StatusCode);
|
||||||
{
|
Assert.NotNull(res.Headers.Location);
|
||||||
options.AuthenticationScheme = "OpenIdConnectHandlerTest";
|
|
||||||
options.AutomaticChallenge = true;
|
setting.ValidateSignoutRedirect(transaction.Response.Headers.Location);
|
||||||
options.ClientId = Guid.NewGuid().ToString();
|
|
||||||
options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager;
|
|
||||||
options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SignOutWithDefaultRedirectUri()
|
public async Task SignOutWithDefaultRedirectUri()
|
||||||
{
|
{
|
||||||
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
|
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
|
||||||
var server = CreateServer(new OpenIdConnectOptions
|
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
|
||||||
{
|
{
|
||||||
Authority = TestDefaultValues.DefaultAuthority,
|
Authority = TestDefaultValues.DefaultAuthority,
|
||||||
ClientId = "Test Id",
|
ClientId = "Test Id",
|
||||||
Configuration = configuration
|
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(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Equal(configuration.EndSessionEndpoint, transaction.Response.Headers.Location.AbsoluteUri);
|
Assert.Equal(configuration.EndSessionEndpoint, transaction.Response.Headers.Location.AbsoluteUri);
|
||||||
}
|
}
|
||||||
|
|
@ -348,8 +64,8 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SignOutWithCustomRedirectUri()
|
public async Task SignOutWithCustomRedirectUri()
|
||||||
{
|
{
|
||||||
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
|
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
|
||||||
var server = CreateServer(new OpenIdConnectOptions
|
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
|
||||||
{
|
{
|
||||||
Authority = TestDefaultValues.DefaultAuthority,
|
Authority = TestDefaultValues.DefaultAuthority,
|
||||||
ClientId = "Test Id",
|
ClientId = "Test Id",
|
||||||
|
|
@ -357,7 +73,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
|
||||||
PostLogoutRedirectUri = "https://example.com/logout"
|
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.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Contains(UrlEncoder.Default.Encode("https://example.com/logout"), transaction.Response.Headers.Location.AbsoluteUri);
|
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]
|
[Fact]
|
||||||
public async Task SignOutWith_Specific_RedirectUri_From_Authentication_Properites()
|
public async Task SignOutWith_Specific_RedirectUri_From_Authentication_Properites()
|
||||||
{
|
{
|
||||||
var configuration = TestUtilities.DefaultOpenIdConnectConfiguration;
|
var configuration = TestDefaultValues.CreateDefaultOpenIdConnectConfiguration();
|
||||||
var server = CreateServer(new OpenIdConnectOptions
|
var server = TestServerBuilder.CreateServer(new OpenIdConnectOptions
|
||||||
{
|
{
|
||||||
Authority = TestDefaultValues.DefaultAuthority,
|
Authority = TestDefaultValues.DefaultAuthority,
|
||||||
ClientId = "Test Id",
|
ClientId = "Test Id",
|
||||||
|
|
@ -374,136 +90,13 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect
|
||||||
PostLogoutRedirectUri = "https://example.com/logout"
|
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.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
|
||||||
Assert.Contains(UrlEncoder.Default.Encode("http://www.example.com/specific_redirect_uri"), transaction.Response.Headers.Location.AbsoluteUri);
|
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
|
// Test Cases for calculating the expiration time of cookie from cookie name
|
||||||
|
[Fact]
|
||||||
public void NonceCookieExpirationTime()
|
public void NonceCookieExpirationTime()
|
||||||
{
|
{
|
||||||
DateTime utcNow = DateTime.UtcNow;
|
DateTime utcNow = DateTime.UtcNow;
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue