Browse Source

Add the OpenIddict endpoints tests

pull/251/head
Kévin Chalet 9 years ago
parent
commit
53b38c93f3
  1. 3
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs
  2. 9
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs
  3. 12
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs
  4. 3
      src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs
  5. 2
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  6. 2
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  7. 2
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  8. 2
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  9. BIN
      test/OpenIddict.Core.Tests/Certificate.pfx
  10. 563
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs
  11. 104
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs
  12. 552
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs
  13. 321
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs
  14. 352
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs
  15. 101
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs
  16. 112
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs
  17. 45
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs
  18. 150
      test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs
  19. 3
      test/OpenIddict.Core.Tests/Placeholder.cs
  20. 7
      test/OpenIddict.Core.Tests/project.json

3
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Authentication.cs

@ -338,7 +338,8 @@ namespace OpenIddict.Infrastructure {
// Create a new authorization request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(
uri: context.HttpContext.Request.PathBase + context.HttpContext.Request.Path,
uri: context.HttpContext.Request.Scheme + "://" + context.HttpContext.Request.Host +
context.HttpContext.Request.PathBase + context.HttpContext.Request.Path,
name: OpenIdConnectConstants.Parameters.RequestId, value: context.Request.RequestId);
context.HttpContext.Response.Redirect(address);

9
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Discovery.cs

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace OpenIddict.Infrastructure {
@ -20,7 +21,8 @@ namespace OpenIddict.Infrastructure {
// OpenIddict disallows the use of the unsecure code_challenge_method=plain method,
// which must be manually removed from the code_challenge_methods_supported property.
// See https://tools.ietf.org/html/rfc7636#section-7.2 for more information.
context.CodeChallengeMethods.Remove(OpenIdConnectConstants.CodeChallengeMethods.Plain);
context.CodeChallengeMethods.Clear();
context.CodeChallengeMethods.Add(OpenIdConnectConstants.CodeChallengeMethods.Sha256);
// Note: the OpenID Connect server middleware automatically populates grant_types_supported
// by determining whether the authorization and token endpoints are enabled or not but
@ -39,8 +41,9 @@ namespace OpenIddict.Infrastructure {
context.Scopes.Add(OpenIdConnectConstants.Scopes.Phone);
context.Scopes.Add(OpenIddictConstants.Scopes.Roles);
// Only add the "offline_access" scope if "refresh_token" is listed as a supported grant type.
if (context.GrantTypes.Contains(OpenIdConnectConstants.GrantTypes.RefreshToken)) {
// Only add the "offline_access" scope if the refresh
// token flow is enabled in the OpenIddict options.
if (services.Options.IsRefreshTokenFlowEnabled()) {
context.Scopes.Add(OpenIdConnectConstants.Scopes.OfflineAccess);
}

12
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Introspection.cs

@ -16,9 +16,7 @@ using Microsoft.Extensions.Logging;
namespace OpenIddict.Infrastructure {
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class {
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
public override Task ExtractIntrospectionRequest([NotNull] ExtractIntrospectionRequestContext context) {
// Note: the OpenID Connect server middleware supports both GET and POST
// introspection requests but OpenIddict only accepts POST requests.
if (!string.Equals(context.HttpContext.Request.Method, "POST", StringComparison.OrdinalIgnoreCase)) {
@ -26,9 +24,15 @@ namespace OpenIddict.Infrastructure {
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Introspection requests must use HTTP POST.");
return;
return Task.FromResult(0);
}
return Task.FromResult(0);
}
public override async Task ValidateIntrospectionRequest([NotNull] ValidateIntrospectionRequestContext context) {
var services = context.HttpContext.RequestServices.GetRequiredService<OpenIddictServices<TApplication, TAuthorization, TScope, TToken>>();
// Note: the OpenID Connect server middleware supports unauthenticated introspection requests
// but OpenIddict uses a stricter policy preventing unauthenticated/public applications
// from using the introspection endpoint, as required by the specifications.

3
src/OpenIddict.Core/Infrastructure/OpenIddictProvider.Session.cs

@ -116,7 +116,8 @@ namespace OpenIddict.Infrastructure {
// Create a new logout request containing only the request_id parameter.
var address = QueryHelpers.AddQueryString(
uri: context.HttpContext.Request.PathBase + context.HttpContext.Request.Path,
uri: context.HttpContext.Request.Scheme + "://" + context.HttpContext.Request.Host +
context.HttpContext.Request.PathBase + context.HttpContext.Request.Path,
name: OpenIdConnectConstants.Parameters.RequestId, value: context.Request.RequestId);
context.HttpContext.Response.Redirect(address);

2
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -24,7 +24,7 @@ namespace OpenIddict {
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictApplicationStore<TApplication> store,
[NotNull] ILogger<OpenIddictApplicationManager<TApplication>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Store = store;
Logger = logger;
}

2
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -21,7 +21,7 @@ namespace OpenIddict {
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictAuthorizationStore<TAuthorization> store,
[NotNull] ILogger<OpenIddictAuthorizationManager<TAuthorization>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}

2
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -21,7 +21,7 @@ namespace OpenIddict {
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictScopeStore<TScope> store,
[NotNull] ILogger<OpenIddictAuthorizationManager<TScope>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}

2
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -22,7 +22,7 @@ namespace OpenIddict {
[NotNull] IServiceProvider services,
[NotNull] IOpenIddictTokenStore<TToken> store,
[NotNull] ILogger<OpenIddictTokenManager<TToken>> logger) {
Context = services?.GetRequiredService<IHttpContextAccessor>()?.HttpContext;
Context = services?.GetService<IHttpContextAccessor>()?.HttpContext;
Logger = logger;
Store = store;
}

BIN
test/OpenIddict.Core.Tests/Certificate.pfx

Binary file not shown.

563
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Authentication.cs

@ -0,0 +1,563 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractAuthorizationRequest_UnsupportedRequestParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
Request = "eyJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwOi8vd3d3LmZhYnJpa2FtLmNvbSIsImF1ZCI6Imh" +
"0dHA6Ly93d3cuY29udG9zby5jb20iLCJyZXNwb25zZV90eXBlIjoiY29kZSIsImNsaWVudF9pZC" +
"I6IkZhYnJpa2FtIiwicmVkaXJlY3RfdXJpIjoiaHR0cDovL3d3dy5mYWJyaWthbS5jb20vcGF0aCJ9.",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
Scope = OpenIdConnectConstants.Scopes.OpenId
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.RequestNotSupported, response.Error);
Assert.Equal("The request parameter is not supported.", response.ErrorDescription);
}
[Fact]
public async Task ExtractAuthorizationRequest_UnsupportedRequestUriParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
RequestUri = "http://www.fabrikam.com/request/GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
Scope = OpenIdConnectConstants.Scopes.OpenId
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.RequestUriNotSupported, response.Error);
Assert.Equal("The request_uri parameter is not supported.", response.ErrorDescription);
}
[Fact]
public async Task ExtractAuthorizationRequest_InvalidRequestIdParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Invalid request: timeout expired.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_UnknownResponseTypeParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = "unknown_response_type"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedResponseType, response.Error);
Assert.Equal("The specified response_type parameter is not supported.", response.ErrorDescription);
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode, "code")]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode, "code id_token")]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode, "code id_token token")]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode, "code token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "code id_token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "code id_token token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "code token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "id_token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "id_token token")]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit, "token")]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCorrespondingFlowIsDisabled(string flow, string type) {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.GrantTypes.Remove(flow));
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
Nonce = "n-0S6_WzA2Mj",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = type,
Scope = OpenIdConnectConstants.Scopes.OpenId
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedResponseType, response.Error);
Assert.Equal("The specified response_type parameter is not allowed.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.GrantTypes.Remove(OpenIdConnectConstants.GrantTypes.RefreshToken));
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The 'offline_access' scope is not allowed.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_UnknownResponseModeParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseMode = "unknown_response_mode",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The specified response_mode parameter is not supported.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsMissing() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = null,
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The required redirect_uri parameter was missing.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsMissing() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
CodeChallengeMethod = null,
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The 'code_challenge_method' parameter must be specified.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenCodeChallengeMethodIsPlain() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
CodeChallengeMethod = OpenIdConnectConstants.CodeChallengeMethods.Plain,
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The specified response_type parameter is not allowed when using PKCE.", response.ErrorDescription);
}
[Theory]
[InlineData("code id_token token")]
[InlineData("code token")]
public async Task ValidateAuthorizationRequest_CodeChallengeRequestWithForbiddenResponseTypeIsRejected(string type) {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
CodeChallenge = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
CodeChallengeMethod = OpenIdConnectConstants.CodeChallengeMethods.Sha256,
Nonce = "n-0S6_WzA2Mj",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = type,
Scope = OpenIdConnectConstants.Scopes.OpenId
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The specified response_type parameter is not allowed when using PKCE.", response.ErrorDescription);
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
}
[Fact]
public async Task ValidateAuthorizationRequest_RequestIsRejectedWhenRedirectUriIsInvalid() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"), Times.Once());
}
[Theory]
[InlineData("code id_token token")]
[InlineData("code token")]
[InlineData("id_token token")]
[InlineData("token")]
public async Task ValidateAuthorizationRequest_ImplicitOrHybridRequestIsRejectedWhenClientIsConfidential(string type) {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
Nonce = "n-0S6_WzA2Mj",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = type,
Scope = OpenIdConnectConstants.Scopes.OpenId
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Confidential clients are not allowed to retrieve " +
"an access token from the authorization endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task HandleAuthorizationRequest_RequestIsPersistedInDistributedCache() {
// Arrange
var cache = new Mock<IDistributedCache>();
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(cache.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Token
});
var identifier = (string) response[OpenIdConnectConstants.Parameters.RequestId];
// Assert
Assert.Equal(1, response.Count());
Assert.NotNull(identifier);
cache.Verify(mock => mock.SetAsync(
OpenIddictConstants.Environment.AuthorizationRequest + identifier,
It.IsAny<byte[]>(),
It.IsAny<DistributedCacheEntryOptions>()), Times.Once());
}
[Theory]
[InlineData("code")]
[InlineData("code id_token")]
[InlineData("code id_token token")]
[InlineData("code token")]
[InlineData("id_token")]
[InlineData("id_token token")]
[InlineData("token")]
public async Task HandleAuthorizationRequest_RequestsAreNotHandledLocally(string type) {
// Arrange
var request = new OpenIdConnectRequest {
ClientId = "Fabrikam",
Nonce = "n-0S6_WzA2Mj",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = type,
Scope = OpenIdConnectConstants.Scopes.OpenId
};
var stream = new MemoryStream();
using (var writer = new BsonWriter(stream)) {
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, request);
}
var cache = new Mock<IDistributedCache>();
cache.Setup(mock => mock.GetAsync(OpenIddictConstants.Environment.AuthorizationRequest +
"b2ee7815-5579-4ff7-86b0-ba671b939d96"))
.ReturnsAsync(stream.ToArray());
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(cache.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
RequestId = "b2ee7815-5579-4ff7-86b0-ba671b939d96"
});
// Assert
Assert.True(!string.IsNullOrEmpty(response.AccessToken) ||
!string.IsNullOrEmpty(response.Code) ||
!string.IsNullOrEmpty(response.IdToken));
}
[Fact]
public async Task ApplyAuthorizationResponse_RequestIsRemovedFromDistributedCache() {
// Arrange
var request = new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Token
};
var stream = new MemoryStream();
using (var writer = new BsonWriter(stream)) {
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, request);
}
var cache = new Mock<IDistributedCache>();
cache.Setup(mock => mock.GetAsync(OpenIddictConstants.Environment.AuthorizationRequest +
"b2ee7815-5579-4ff7-86b0-ba671b939d96"))
.ReturnsAsync(stream.ToArray());
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(cache.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
RequestId = "b2ee7815-5579-4ff7-86b0-ba671b939d96"
});
// Assert
Assert.NotNull(response.AccessToken);
cache.Verify(mock => mock.RemoveAsync(
OpenIddictConstants.Environment.AuthorizationRequest +
"b2ee7815-5579-4ff7-86b0-ba671b939d96"), Times.Once());
}
[Fact]
public async Task ApplyAuthorizationResponse_ErroredRequestIsNotHandledLocallyWhenStatusCodeMiddlewareIsEnabled() {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.EnableAuthorizationEndpoint("/authorize-status-code-middleware");
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync("/authorize-status-code-middleware", new OpenIdConnectRequest {
ClientId = null,
RedirectUri = null,
ResponseType = null
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, (string) response["error_custom"]);
}
}
}

104
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Discovery.cs

@ -0,0 +1,104 @@
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task HandleConfigurationRequest_PlainCodeChallengeMethodIsNotReturned() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(ConfigurationEndpoint);
// Assert
Assert.DoesNotContain(
OpenIdConnectConstants.CodeChallengeMethods.Plain,
response[OpenIdConnectConstants.Metadata.CodeChallengeMethodsSupported].Values<string>());
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Implicit)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
public async Task HandleConfigurationRequest_EnabledFlowsAreReturned(string flow) {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => {
options.GrantTypes.Clear();
options.GrantTypes.Add(flow);
});
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(ConfigurationEndpoint);
var types = response[OpenIdConnectConstants.Metadata.GrantTypesSupported].Values<string>();
// Assert
Assert.Equal(1, types.Count());
Assert.Contains(flow, types);
}
[Theory]
[InlineData(OpenIdConnectConstants.Scopes.Profile)]
[InlineData(OpenIdConnectConstants.Scopes.Email)]
[InlineData(OpenIdConnectConstants.Scopes.Phone)]
[InlineData(OpenIddictConstants.Scopes.Roles)]
public async Task HandleConfigurationRequest_StandardScopesAreExposed(string scope) {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(ConfigurationEndpoint);
// Assert
Assert.Contains(scope, response[OpenIdConnectConstants.Metadata.ScopesSupported].Values<string>());
}
[Fact]
public async Task HandleConfigurationRequest_OfflineAccessScopeIsReturnedWhenRefreshTokenFlowIsEnabled() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(ConfigurationEndpoint);
// Assert
Assert.Contains(OpenIdConnectConstants.Scopes.OfflineAccess,
response[OpenIdConnectConstants.Metadata.ScopesSupported].Values<string>());
}
[Fact]
public async Task HandleConfigurationRequest_OfflineAccessScopeIsReturnedWhenRefreshTokenFlowIsDisabled() {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => {
// Note: at least one flow must be enabled.
options.GrantTypes.Clear();
options.GrantTypes.Add(OpenIdConnectConstants.GrantTypes.AuthorizationCode);
});
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(ConfigurationEndpoint);
// Assert
Assert.DoesNotContain(OpenIdConnectConstants.Scopes.OfflineAccess,
response[OpenIdConnectConstants.Metadata.ScopesSupported].Values<string>());
}
}
}

552
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Exchange.cs

@ -0,0 +1,552 @@
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.AuthorizationCode)]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData(OpenIdConnectConstants.GrantTypes.RefreshToken)]
public async Task ValidateTokenRequest_RequestIsRejectedWhenFlowIsNotEnabled(string flow) {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.GrantTypes.Remove(flow));
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = flow,
Username = "johndoe",
Password = "A3ddj3w",
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedGrantType, response.Error);
Assert.Equal("The specified grant_type is not supported by this authorization server.", response.ErrorDescription);
}
[Fact]
public async Task ValidateTokenRequest_RequestWithOfflineAccessScopeIsRejectedWhenRefreshTokenFlowIsDisabled() {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.GrantTypes.Remove(OpenIdConnectConstants.GrantTypes.RefreshToken));
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The 'offline_access' scope is not allowed.", response.ErrorDescription);
}
[Fact]
public async Task ValidateTokenRequest_ClientCredentialsRequestWithOfflineAccessScopeIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.ClientCredentials,
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The 'offline_access' scope is not allowed when using grant_type=client_credentials.", response.ErrorDescription);
}
[Theory]
[InlineData("client_id", "")]
[InlineData("", "client_secret")]
public async Task ValidateTokenRequest_ClientCredentialsRequestIsRejectedWhenCredentialsAreMissing(string identifier, string secret) {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = identifier,
ClientSecret = secret,
GrantType = OpenIdConnectConstants.GrantTypes.ClientCredentials
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Client applications must be authenticated to use the client credentials grant.", response.ErrorDescription);
}
[Fact]
public async Task ValidateTokenRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() {
// Arrange
var server = CreateAuthorizationServer(builder => builder.RequireClientIdentification());
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = null,
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The mandatory 'client_id' parameter was missing.", response.ErrorDescription);
}
[Fact]
public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
}
[Fact]
public async Task ValidateTokenRequest_ClientCredentialsRequestFromPublicClientIsRejected() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = OpenIdConnectConstants.GrantTypes.ClientCredentials
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnauthorizedClient, response.Error);
Assert.Equal("Public clients are not allowed to use the client credentials grant.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateTokenRequest_ClientSecretCannotBeUsedByPublicClients() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Public clients are not allowed to send a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateTokenRequest_ClientSecretIsRequiredForConfidentialClients() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = null,
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Missing credentials: ensure that you specified a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateTokenRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsExpired() {
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The authorization code is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsExpired() {
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The refresh token is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_AuthorizationCodeIsAutomaticallyRevoked() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var token = Mock.Of<object>();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode
});
// Assert
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRevokedWhenSlidingExpirationIsEnabled() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = Mock.Of<object>();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
}
[Theory]
[InlineData(OpenIdConnectConstants.GrantTypes.ClientCredentials)]
[InlineData(OpenIdConnectConstants.GrantTypes.Password)]
[InlineData("urn:ietf:params:oauth:grant-type:custom_grant")]
public async Task HandleTokenRequest_RequestsAreNotHandledLocally(string flow) {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(true);
}));
builder.AllowCustomFlow("urn:ietf:params:oauth:grant-type:custom_grant");
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
GrantType = flow,
Username = "johndoe",
Password = "A3ddj3w"
});
// Assert
Assert.NotNull(response.AccessToken);
}
}
}

321
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Introspection.cs

@ -0,0 +1,321 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractIntrospectionRequest_GetRequestsAreRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.GetAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Introspection requests must use HTTP POST.", response.ErrorDescription);
}
[Theory]
[InlineData("client_id", "")]
[InlineData("", "client_secret")]
public async Task ValidateIntrospectionRequest_ClientCredentialsRequestIsRejectedWhenCredentialsAreMissing(string identifier, string secret) {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = identifier,
ClientSecret = secret,
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Clients must be authenticated to use the introspection endpoint.", response.ErrorDescription);
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestsSentByPublicClientsAreRejected() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Public applications are not allowed to use the introspection endpoint.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenClientIsNotAValidAudience() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetAudiences("Contoso");
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(true);
}));
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(1, response.Count());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCodeIsRevoked() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(1, response.Count());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
}
[Fact]
public async Task HandleIntrospectionRequest_RequestIsRejectedWhenRefreshTokenIsRevoked() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("2YotnFZFEjr1zCsicMWpAA"))
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(IntrospectionEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(1, response.Count());
Assert.False((bool) response[OpenIdConnectConstants.Claims.Active]);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
}
}
}

352
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Revocation.cs

@ -0,0 +1,352 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Theory]
[InlineData(OpenIdConnectConstants.TokenTypeHints.AccessToken)]
[InlineData(OpenIdConnectConstants.TokenTypeHints.IdToken)]
public async Task ValidateRevocationRequest_UnknownTokenTokenHintIsRejected(string hint) {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG",
TokenTypeHint = hint
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedTokenType, response.Error);
Assert.Equal("Only authorization codes and refresh tokens can be revoked. When specifying a token_type_hint " +
"parameter, its value must be equal to 'authorization_code' or 'refresh_token'.", response.ErrorDescription);
}
[Fact]
public async Task ValidateRevocationRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() {
// Arrange
var server = CreateAuthorizationServer(builder => builder.RequireClientIdentification());
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG",
TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("The mandatory 'client_id' parameter was missing.", response.ErrorDescription);
}
[Fact]
public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCannotBeFound() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
Token = "SlAV32hkKG",
TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Application not found in the database: ensure that your client_id is correct.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
}
[Fact]
public async Task ValidateRevocationRequest_ClientSecretCannotBeUsedByPublicClients() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "SlAV32hkKG",
TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Public clients are not allowed to send a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateRevocationRequest_ClientSecretIsRequiredForConfidentialClients() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = null,
Token = "SlAV32hkKG",
TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Missing credentials: ensure that you specified a client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
}
[Fact]
public async Task ValidateRevocationRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() {
// Arrange
var application = Mock.Of<object>();
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"))
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "SlAV32hkKG",
TokenTypeHint = OpenIdConnectConstants.TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid credentials: ensure that you specified a correct client_secret.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByClientIdAsync("Fabrikam"), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetClientTypeAsync(application), Times.Once());
Mock.Get(manager).Verify(mock => mock.ValidateSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw"), Times.Once());
}
[Fact]
public async Task HandleRevocationRequest_RequestIsRejectedWhenTokenIsAnAccessToken() {
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.AccessToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SlAV32hkKG"))
.Returns(ticket);
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedTokenType, response.Error);
Assert.Equal("Only authorization codes and refresh tokens can be revoked.", response.ErrorDescription);
format.Verify(mock => mock.Unprotect("SlAV32hkKG"), Times.Once());
}
[Fact]
public async Task HandleRevocationRequest_RequestIsNotRejectedWhenTokenIsAnIdentityToken() {
// Arrange
var token = Mock.Of<SecurityToken>(mock =>
mock.ValidFrom == DateTime.UtcNow.AddDays(-1) &&
mock.ValidTo == DateTime.UtcNow.AddDays(1));
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Usage, OpenIdConnectConstants.Usages.IdentityToken);
var handler = new Mock<JwtSecurityTokenHandler>();
handler.Setup(mock => mock.CanReadToken("SlAV32hkKG"))
.Returns(true);
handler.As<ISecurityTokenValidator>()
.Setup(mock => mock.ValidateToken("SlAV32hkKG", It.IsAny<TokenValidationParameters>(), out token))
.Returns(new ClaimsPrincipal(identity));
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.IdentityTokenHandler = handler.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.UnsupportedTokenType, response.Error);
Assert.Equal("Only authorization codes and refresh tokens can be revoked.", response.ErrorDescription);
handler.As<ISecurityTokenValidator>()
.Verify(mock => mock.CanReadToken("SlAV32hkKG"), Times.Once());
handler.As<ISecurityTokenValidator>()
.Verify(mock => mock.ValidateToken("SlAV32hkKG", It.IsAny<TokenValidationParameters>(), out token), Times.Once());
}
[Fact]
public async Task HandleRevocationRequest_TokenIsNotRevokedWhenItIsAlreadyInvalid() {
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SlAV32hkKG"))
.Returns(ticket);
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG"
});
// Assert
Assert.Equal(0, response.Count());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(It.IsAny<object>()), Times.Never());
}
[Fact]
public async Task HandleRevocationRequest_TokenIsSuccessfullyRevoked() {
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTicketId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetUsage(OpenIdConnectConstants.Usages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SlAV32hkKG"))
.Returns(ticket);
var token = Mock.Of<object>();
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(RevocationEndpoint, new OpenIdConnectRequest {
Token = "SlAV32hkKG"
});
// Assert
Assert.Equal(0, response.Count());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56"), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(token), Times.Once());
}
}
}

101
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Serialization.cs

@ -0,0 +1,101 @@
using System.IO;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task SerializeAuthorizationCode_AuthorizationCodeIsAutomaticallyPersisted() {
// Arrange
var request = new OpenIdConnectRequest {
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code
};
var stream = new MemoryStream();
using (var writer = new BsonWriter(stream)) {
writer.CloseOutput = false;
var serializer = JsonSerializer.CreateDefault();
serializer.Serialize(writer, request);
}
var cache = new Mock<IDistributedCache>();
cache.Setup(mock => mock.GetAsync(OpenIddictConstants.Environment.AuthorizationRequest +
"b2ee7815-5579-4ff7-86b0-ba671b939d96"))
.ReturnsAsync(stream.ToArray());
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam"))
.ReturnsAsync(application);
instance.Setup(mock => mock.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path"))
.ReturnsAsync(true);
instance.Setup(mock => mock.GetClientTypeAsync(application))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Services.AddSingleton(cache.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest {
RequestId = "b2ee7815-5579-4ff7-86b0-ba671b939d96"
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_RefreshTokenIsAutomaticallyPersisted() {
// Arrange
var manager = CreateTokenManager(instance => {
instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest {
GrantType = OpenIdConnectConstants.GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
Scope = OpenIdConnectConstants.Scopes.OfflineAccess
});
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken), Times.Once());
}
}
}

112
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Session.cs

@ -0,0 +1,112 @@
using System.Linq;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task ExtractLogoutRequest_InvalidRequestIdParameterIsRejected() {
// Arrange
var server = CreateAuthorizationServer();
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(LogoutEndpoint, new OpenIdConnectRequest {
RequestId = "EFAF3596-F868-497F-96BB-AA2AD1F8B7E7"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, response.Error);
Assert.Equal("Invalid request: timeout expired.", response.ErrorDescription);
}
[Fact]
public async Task ValidateLogoutRequest_RequestIsRejectedWhenRedirectUriIsInvalid() {
// Arrange
var manager = CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
.ReturnsAsync(null);
});
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(LogoutEndpoint, new OpenIdConnectRequest {
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidClient, response.Error);
Assert.Equal("Invalid post_logout_redirect_uri.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"), Times.Once());
}
[Fact]
public async Task HandleLogoutRequest_RequestIsPersistedInDistributedCache() {
// Arrange
var cache = new Mock<IDistributedCache>();
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
var application = Mock.Of<object>();
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
.ReturnsAsync(application);
}));
builder.Services.AddSingleton(cache.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(LogoutEndpoint, new OpenIdConnectRequest {
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
var identifier = (string) response[OpenIdConnectConstants.Parameters.RequestId];
// Assert
Assert.Equal(1, response.Count());
Assert.NotNull(identifier);
cache.Verify(mock => mock.SetAsync(
OpenIddictConstants.Environment.LogoutRequest + identifier,
It.IsAny<byte[]>(),
It.IsAny<DistributedCacheEntryOptions>()), Times.Once());
}
[Fact]
public async Task ApplyLogoutResponse_ErroredRequestIsNotHandledLocallyWhenStatusCodeMiddlewareIsEnabled() {
// Arrange
var server = CreateAuthorizationServer(builder => {
builder.Services.AddSingleton(CreateApplicationManager(instance => {
instance.Setup(mock => mock.FindByLogoutRedirectUri("http://www.fabrikam.com/path"))
.ReturnsAsync(null);
}));
builder.EnableAuthorizationEndpoint("/logout-status-code-middleware");
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync("/logout-status-code-middleware", new OpenIdConnectRequest {
PostLogoutRedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidRequest, (string) response["error_custom"]);
}
}
}

45
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.Userinfo.cs

@ -0,0 +1,45 @@
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Moq;
using Xunit;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
[Fact]
public async Task HandleUserinfoRequest_RequestIsHandledByUserCode() {
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SlAV32hkKG"))
.Returns(ticket);
var server = CreateAuthorizationServer(builder => {
builder.Configure(options => options.AccessTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(UserinfoEndpoint, new OpenIdConnectRequest {
AccessToken = "SlAV32hkKG"
});
// Assert
Assert.Equal("Bob le Bricoleur", (string) response[OpenIdConnectConstants.Claims.Subject]);
format.Verify(mock => mock.Unprotect("SlAV32hkKG"), Times.Once());
}
}
}

150
test/OpenIddict.Core.Tests/Infrastructure/OpenIddictProviderTests.cs

@ -0,0 +1,150 @@
using System;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Newtonsoft.Json;
namespace OpenIddict.Core.Tests.Infrastructure {
public partial class OpenIddictProviderTests {
public const string AuthorizationEndpoint = "/connect/authorize";
public const string ConfigurationEndpoint = "/.well-known/openid-configuration";
public const string IntrospectionEndpoint = "/connect/introspect";
public const string LogoutEndpoint = "/connect/logout";
public const string RevocationEndpoint = "/connect/revoke";
public const string TokenEndpoint = "/connect/token";
public const string UserinfoEndpoint = "/connect/userinfo";
private static TestServer CreateAuthorizationServer(Action<OpenIddictBuilder> configuration = null) {
var builder = new WebHostBuilder();
builder.UseEnvironment("Testing");
builder.ConfigureLogging(options => options.AddDebug());
builder.ConfigureServices(services => {
var instance = services.AddOpenIddict<object, object, object, object>()
// Disable the transport security requirement during testing.
.DisableHttpsRequirement()
// Enable the tested endpoints.
.EnableAuthorizationEndpoint(AuthorizationEndpoint)
.EnableIntrospectionEndpoint(IntrospectionEndpoint)
.EnableLogoutEndpoint(LogoutEndpoint)
.EnableRevocationEndpoint(RevocationEndpoint)
.EnableTokenEndpoint(TokenEndpoint)
.EnableUserinfoEndpoint(UserinfoEndpoint)
// Enable the tested flows.
.AllowAuthorizationCodeFlow()
.AllowClientCredentialsFlow()
.AllowImplicitFlow()
.AllowPasswordFlow()
.AllowRefreshTokenFlow()
// Register the X.509 certificate used to sign the identity tokens.
.AddSigningCertificate(
assembly: typeof(OpenIddictProviderTests).GetTypeInfo().Assembly,
resource: "OpenIddict.Core.Tests.Certificate.pfx",
password: "OpenIddict")
// Note: overriding the default data protection provider is not necessary for the tests to pass,
// but is useful to ensure unnecessary keys are not persisted in testing environments, which also
// helps make the unit tests run faster, as no registry or disk access is required in this case.
.UseDataProtectionProvider(new EphemeralDataProtectionProvider());
// Run the configuration delegate
// registered by the unit tests.
configuration?.Invoke(instance);
});
builder.Configure(app => {
app.UseStatusCodePages(context => {
context.HttpContext.Response.Headers[HeaderNames.ContentType] = "application/json";
return context.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(new {
error_custom = OpenIdConnectConstants.Errors.InvalidRequest
}));
});
app.Use(next => context => {
if (context.Request.Path != "/authorize-status-code-middleware" &&
context.Request.Path != "/logout-status-code-middleware") {
var feature = context.Features.Get<IStatusCodePagesFeature>();
feature.Enabled = false;
}
return next(context);
});
app.UseOpenIddict();
app.Run(context => {
if (context.Request.Path == AuthorizationEndpoint ||
context.Request.Path == TokenEndpoint) {
var request = context.GetOpenIdConnectRequest();
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.NameIdentifier, "Bob le Magnifique");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetScopes(request.GetScopes());
return context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
}
else if (context.Request.Path == UserinfoEndpoint) {
context.Response.Headers[HeaderNames.ContentType] = "application/json";
return context.Response.WriteAsync(JsonConvert.SerializeObject(new {
sub = "Bob le Bricoleur"
}));
}
return Task.FromResult(0);
});
});
return new TestServer(builder);
}
private static OpenIddictApplicationManager<object> CreateApplicationManager(Action<Mock<OpenIddictApplicationManager<object>>> setup = null) {
var manager = new Mock<OpenIddictApplicationManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictApplicationStore<object>>(),
Mock.Of<ILogger<OpenIddictApplicationManager<object>>>());
setup?.Invoke(manager);
return manager.Object;
}
private static OpenIddictTokenManager<object> CreateTokenManager(Action<Mock<OpenIddictTokenManager<object>>> setup = null) {
var manager = new Mock<OpenIddictTokenManager<object>>(
Mock.Of<IServiceProvider>(),
Mock.Of<IOpenIddictTokenStore<object>>(),
Mock.Of<ILogger<OpenIddictTokenManager<object>>>());
setup?.Invoke(manager);
return manager.Object;
}
}
}

3
test/OpenIddict.Core.Tests/Placeholder.cs

@ -1,3 +0,0 @@
namespace OpenIddict.Core.Tests {
public class Placeholder { }
}

7
test/OpenIddict.Core.Tests/project.json

@ -1,10 +1,15 @@
{
"buildOptions": {
"warningsAsErrors": true
"warningsAsErrors": true,
"embed": {
"include": [ "Certificate.pfx" ]
}
},
"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.TestHost": "1.0.0",
"Microsoft.Extensions.Caching.Memory": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",

Loading…
Cancel
Save