18 changed files with 1068 additions and 1179 deletions
@ -0,0 +1,95 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using AspNet.Security.OAuth.Validation; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using OpenIddict.Abstractions; |
||||
|
|
||||
|
namespace OpenIddict.Validation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides the logic necessary to extract, validate and handle OAuth2 requests.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public class OpenIddictValidationEvents : OAuthValidationEvents |
||||
|
{ |
||||
|
public override async Task DecryptToken([NotNull] DecryptTokenContext context) |
||||
|
{ |
||||
|
var options = (OpenIddictValidationOptions) context.Options; |
||||
|
if (options.UseReferenceTokens) |
||||
|
{ |
||||
|
// Note: the token manager is deliberately not injected using constructor injection
|
||||
|
// to allow using the validation handler without having to register the core services.
|
||||
|
var manager = context.HttpContext.RequestServices.GetService<IOpenIddictTokenManager>(); |
||||
|
if (manager == null) |
||||
|
{ |
||||
|
throw new InvalidOperationException(new StringBuilder() |
||||
|
.AppendLine("The core services must be registered when enabling reference tokens support.") |
||||
|
.Append("To register the OpenIddict core services, use 'services.AddOpenIddict().AddCore()'.") |
||||
|
.ToString()); |
||||
|
} |
||||
|
|
||||
|
// Retrieve the token entry from the database. If it
|
||||
|
// cannot be found, assume the token is not valid.
|
||||
|
var token = await manager.FindByReferenceIdAsync(context.Token); |
||||
|
if (token == null) |
||||
|
{ |
||||
|
context.Fail("Authentication failed because the access token cannot be found in the database."); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Extract the encrypted payload from the token. If it's null or empty,
|
||||
|
// assume the token is not a reference token and consider it as invalid.
|
||||
|
var payload = await manager.GetPayloadAsync(token); |
||||
|
if (string.IsNullOrEmpty(payload)) |
||||
|
{ |
||||
|
context.Fail("Authentication failed because the access token is not a reference token."); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var ticket = context.DataFormat.Unprotect(payload); |
||||
|
if (ticket == null) |
||||
|
{ |
||||
|
context.Fail("Authentication failed because the reference token cannot be decrypted. " + |
||||
|
"This may indicate that the token entry is corrupted or tampered."); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Dynamically set the creation and expiration dates.
|
||||
|
ticket.Properties.IssuedUtc = await manager.GetCreationDateAsync(token); |
||||
|
ticket.Properties.ExpiresUtc = await manager.GetExpirationDateAsync(token); |
||||
|
|
||||
|
// Restore the token and authorization identifiers attached with the database entry.
|
||||
|
ticket.Properties.SetProperty(OpenIddictConstants.Properties.TokenId, await manager.GetIdAsync(token)); |
||||
|
ticket.Properties.SetProperty(OpenIddictConstants.Properties.AuthorizationId, |
||||
|
await manager.GetAuthorizationIdAsync(token)); |
||||
|
|
||||
|
context.Principal = ticket.Principal; |
||||
|
context.Properties = ticket.Properties; |
||||
|
context.Success(); |
||||
|
} |
||||
|
|
||||
|
await base.DecryptToken(context); |
||||
|
} |
||||
|
|
||||
|
public void Import([NotNull] OAuthValidationEvents events) |
||||
|
{ |
||||
|
OnApplyChallenge = events.ApplyChallenge; |
||||
|
OnCreateTicket = events.CreateTicket; |
||||
|
OnDecryptToken = events.DecryptToken; |
||||
|
OnRetrieveToken = events.RetrieveToken; |
||||
|
OnValidateToken = events.ValidateToken; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,338 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Net; |
||||
|
using System.Net.Http; |
||||
|
using System.Net.Http.Headers; |
||||
|
using System.Security.Claims; |
||||
|
using System.Text; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using AspNet.Security.OAuth.Validation; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.DataProtection; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Microsoft.AspNetCore.TestHost; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Moq; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using OpenIddict.Abstractions; |
||||
|
using OpenIddict.Core; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace OpenIddict.Validation.Tests |
||||
|
{ |
||||
|
public class OpenIddictValidationEventsTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task DecryptToken_ThrowsAnExceptionWhenTokenManagerIsNotRegistered() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var server = CreateResourceServer(builder => |
||||
|
{ |
||||
|
builder.Services.RemoveAll(typeof(IOpenIddictTokenManager)); |
||||
|
}); |
||||
|
|
||||
|
var client = server.CreateClient(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-reference-token-id"); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate |
||||
|
{ |
||||
|
return client.SendAsync(request); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(new StringBuilder() |
||||
|
.AppendLine("The core services must be registered when enabling reference tokens support.") |
||||
|
.Append("To register the OpenIddict core services, use 'services.AddOpenIddict().AddCore()'.") |
||||
|
.ToString(), exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DecryptToken_ReturnsFailedResultForUnknownReferenceToken() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var manager = CreateTokenManager(instance => |
||||
|
{ |
||||
|
instance.Setup(mock => mock.FindByReferenceIdAsync("invalid-reference-token-id", It.IsAny<CancellationToken>())) |
||||
|
.ReturnsAsync(value: null); |
||||
|
}); |
||||
|
|
||||
|
var server = CreateResourceServer(builder => |
||||
|
{ |
||||
|
builder.Services.AddSingleton(manager); |
||||
|
}); |
||||
|
|
||||
|
var client = server.CreateClient(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-reference-token-id"); |
||||
|
|
||||
|
// Act
|
||||
|
var response = await client.SendAsync(request); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
||||
|
|
||||
|
Mock.Get(manager).Verify(mock => mock.FindByReferenceIdAsync("invalid-reference-token-id", It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DecryptToken_ReturnsFailedResultForNonReferenceToken() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var token = new OpenIddictToken(); |
||||
|
|
||||
|
var manager = CreateTokenManager(instance => |
||||
|
{ |
||||
|
instance.Setup(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>())) |
||||
|
.ReturnsAsync(token); |
||||
|
|
||||
|
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>())) |
||||
|
.Returns(new ValueTask<string>(result: null)); |
||||
|
}); |
||||
|
|
||||
|
var server = CreateResourceServer(builder => |
||||
|
{ |
||||
|
builder.Services.AddSingleton(manager); |
||||
|
}); |
||||
|
|
||||
|
var client = server.CreateClient(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-reference-token-id"); |
||||
|
|
||||
|
// Act
|
||||
|
var response = await client.SendAsync(request); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
||||
|
|
||||
|
Mock.Get(manager).Verify(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
Mock.Get(manager).Verify(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DecryptToken_ReturnsFailedResultForInvalidReferenceTokenPayload() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var token = new OpenIddictToken(); |
||||
|
|
||||
|
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>(); |
||||
|
format.Setup(mock => mock.Unprotect("invalid-reference-token-payload")) |
||||
|
.Returns(value: null); |
||||
|
|
||||
|
var manager = CreateTokenManager(instance => |
||||
|
{ |
||||
|
instance.Setup(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>())) |
||||
|
.ReturnsAsync(token); |
||||
|
|
||||
|
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>())) |
||||
|
.Returns(new ValueTask<string>("invalid-reference-token-payload")); |
||||
|
}); |
||||
|
|
||||
|
var server = CreateResourceServer(builder => |
||||
|
{ |
||||
|
builder.Services.AddSingleton(manager); |
||||
|
builder.Configure(options => options.AccessTokenFormat = format.Object); |
||||
|
}); |
||||
|
|
||||
|
var client = server.CreateClient(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-reference-token-id"); |
||||
|
|
||||
|
// Act
|
||||
|
var response = await client.SendAsync(request); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
||||
|
|
||||
|
Mock.Get(manager).Verify(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
Mock.Get(manager).Verify(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
format.Verify(mock => mock.Unprotect("invalid-reference-token-payload"), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task DecryptToken_ReturnsValidResultForValidReferenceToken() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var token = new OpenIddictToken(); |
||||
|
|
||||
|
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>(); |
||||
|
format.Setup(mock => mock.Unprotect("valid-reference-token-payload")) |
||||
|
.Returns(delegate |
||||
|
{ |
||||
|
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
||||
|
|
||||
|
return new AuthenticationTicket( |
||||
|
new ClaimsPrincipal(identity), |
||||
|
OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
}); |
||||
|
|
||||
|
var manager = CreateTokenManager(instance => |
||||
|
{ |
||||
|
instance.Setup(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>())) |
||||
|
.ReturnsAsync(token); |
||||
|
|
||||
|
instance.Setup(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>())) |
||||
|
.Returns(new ValueTask<string>("valid-reference-token-payload")); |
||||
|
|
||||
|
instance.Setup(mock => mock.GetCreationDateAsync(token, It.IsAny<CancellationToken>())) |
||||
|
.Returns(new ValueTask<DateTimeOffset?>(new DateTimeOffset(2018, 01, 01, 00, 00, 00, TimeSpan.Zero))); |
||||
|
|
||||
|
instance.Setup(mock => mock.GetExpirationDateAsync(token, It.IsAny<CancellationToken>())) |
||||
|
.Returns(new ValueTask<DateTimeOffset?>(new DateTimeOffset(2918, 01, 01, 00, 00, 00, TimeSpan.Zero))); |
||||
|
}); |
||||
|
|
||||
|
var server = CreateResourceServer(builder => |
||||
|
{ |
||||
|
builder.Services.AddSingleton(manager); |
||||
|
builder.Configure(options => options.AccessTokenFormat = format.Object); |
||||
|
}); |
||||
|
|
||||
|
var client = server.CreateClient(); |
||||
|
|
||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "/ticket"); |
||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-reference-token-id"); |
||||
|
|
||||
|
// Act
|
||||
|
var response = await client.SendAsync(request); |
||||
|
|
||||
|
var ticket = JObject.Parse(await response.Content.ReadAsStringAsync()); |
||||
|
var properties = (from property in ticket.Value<JArray>("Properties") |
||||
|
select new |
||||
|
{ |
||||
|
Name = property.Value<string>("Name"), |
||||
|
Value = property.Value<string>("Value") |
||||
|
}).ToDictionary(property => property.Name, property => property.Value); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
||||
|
|
||||
|
Assert.Equal( |
||||
|
new DateTimeOffset(2018, 01, 01, 00, 00, 00, TimeSpan.Zero), |
||||
|
DateTimeOffset.Parse(properties[".issued"], CultureInfo.InvariantCulture)); |
||||
|
Assert.Equal( |
||||
|
new DateTimeOffset(2918, 01, 01, 00, 00, 00, TimeSpan.Zero), |
||||
|
DateTimeOffset.Parse(properties[".expires"], CultureInfo.InvariantCulture)); |
||||
|
|
||||
|
Mock.Get(manager).Verify(mock => mock.FindByReferenceIdAsync("valid-reference-token-id", It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
Mock.Get(manager).Verify(mock => mock.GetPayloadAsync(token, It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
Mock.Get(manager).Verify(mock => mock.GetCreationDateAsync(token, It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
Mock.Get(manager).Verify(mock => mock.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()), Times.Once()); |
||||
|
format.Verify(mock => mock.Unprotect("valid-reference-token-payload"), Times.Once()); |
||||
|
} |
||||
|
|
||||
|
private static TestServer CreateResourceServer(Action<OpenIddictValidationBuilder> configuration = null) |
||||
|
{ |
||||
|
var builder = new WebHostBuilder(); |
||||
|
builder.UseEnvironment("Testing"); |
||||
|
|
||||
|
builder.ConfigureLogging(options => options.AddDebug()); |
||||
|
|
||||
|
builder.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddOpenIddict() |
||||
|
.AddCore(options => |
||||
|
{ |
||||
|
options.SetDefaultTokenEntity<OpenIddictToken>(); |
||||
|
options.Services.AddSingleton(CreateTokenManager()); |
||||
|
}) |
||||
|
|
||||
|
.AddValidation(options => |
||||
|
{ |
||||
|
options.UseReferenceTokens(); |
||||
|
|
||||
|
// 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.
|
||||
|
options.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); |
||||
|
|
||||
|
// Run the configuration delegate
|
||||
|
// registered by the unit tests.
|
||||
|
configuration?.Invoke(options); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
builder.Configure(app => |
||||
|
{ |
||||
|
app.Map("/ticket", map => map.Run(async context => |
||||
|
{ |
||||
|
var result = await context.AuthenticateAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
if (result.Principal == null) |
||||
|
{ |
||||
|
await context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
context.Response.ContentType = "application/json"; |
||||
|
|
||||
|
// Return the authentication ticket as a JSON object.
|
||||
|
await context.Response.WriteAsync(JsonConvert.SerializeObject(new |
||||
|
{ |
||||
|
Claims = from claim in result.Principal.Claims |
||||
|
select new { claim.Type, claim.Value }, |
||||
|
|
||||
|
Properties = from property in result.Properties.Items |
||||
|
select new { Name = property.Key, property.Value } |
||||
|
})); |
||||
|
})); |
||||
|
|
||||
|
app.Run(async context => |
||||
|
{ |
||||
|
var result = await context.AuthenticateAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
if (result.Principal == null) |
||||
|
{ |
||||
|
await context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var subject = result.Principal.FindFirst(OAuthValidationConstants.Claims.Subject)?.Value; |
||||
|
if (string.IsNullOrEmpty(subject)) |
||||
|
{ |
||||
|
await context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await context.Response.WriteAsync(subject); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
return new TestServer(builder); |
||||
|
} |
||||
|
|
||||
|
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager( |
||||
|
Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> configuration = null) |
||||
|
{ |
||||
|
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>( |
||||
|
Mock.Of<IOpenIddictTokenStoreResolver>(), |
||||
|
Mock.Of<ILogger<OpenIddictTokenManager<OpenIddictToken>>>(), |
||||
|
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
||||
|
|
||||
|
configuration?.Invoke(manager); |
||||
|
|
||||
|
return manager.Object; |
||||
|
} |
||||
|
|
||||
|
public class OpenIddictToken { } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,110 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using AspNet.Security.OAuth.Validation; |
||||
|
using AspNet.Security.OpenIdConnect.Client; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.AspNetCore.Hosting; |
||||
|
using Microsoft.AspNetCore.TestHost; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace OpenIddict.Validation.Tests |
||||
|
{ |
||||
|
public class OpenIddictValidationInitializerTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task PostConfigure_ThrowsAnExceptionWhenApplicationEventsTypeAndInstanceAreProvided() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var server = CreateAuthorizationServer(builder => |
||||
|
{ |
||||
|
builder.Configure(options => |
||||
|
{ |
||||
|
options.ApplicationEvents = new OAuthValidationEvents(); |
||||
|
options.ApplicationEventsType = typeof(OAuthValidationEvents); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
var client = new OpenIdConnectClient(server.CreateClient()); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate |
||||
|
{ |
||||
|
return client.GetAsync("/"); |
||||
|
}); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal("Application events cannot be registered when a type is specified.", exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task PostConfigure_ThrowsAnExceptionForInvalidApplicationEventsType() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var server = CreateAuthorizationServer(builder => |
||||
|
{ |
||||
|
builder.Configure(options => options.ApplicationEventsType = typeof(object)); |
||||
|
}); |
||||
|
|
||||
|
var client = new OpenIdConnectClient(server.CreateClient()); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate |
||||
|
{ |
||||
|
return client.GetAsync("/"); |
||||
|
}); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal("Application events must inherit from OAuthValidationEvents.", exception.Message); |
||||
|
} |
||||
|
|
||||
|
private static TestServer CreateAuthorizationServer(Action<OpenIddictValidationBuilder> configuration = null) |
||||
|
{ |
||||
|
var builder = new WebHostBuilder(); |
||||
|
|
||||
|
builder.UseEnvironment("Testing"); |
||||
|
|
||||
|
builder.ConfigureLogging(options => options.AddDebug()); |
||||
|
|
||||
|
builder.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddAuthentication(); |
||||
|
services.AddOptions(); |
||||
|
services.AddDistributedMemoryCache(); |
||||
|
|
||||
|
services.AddOpenIddict() |
||||
|
.AddCore(options => |
||||
|
{ |
||||
|
options.SetDefaultApplicationEntity<OpenIddictApplication>() |
||||
|
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>() |
||||
|
.SetDefaultScopeEntity<OpenIddictScope>() |
||||
|
.SetDefaultTokenEntity<OpenIddictToken>(); |
||||
|
}) |
||||
|
|
||||
|
.AddValidation(options => configuration?.Invoke(options)); |
||||
|
}); |
||||
|
|
||||
|
builder.Configure(app => |
||||
|
{ |
||||
|
app.UseAuthentication(); |
||||
|
|
||||
|
app.Run(context => context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme)); |
||||
|
}); |
||||
|
|
||||
|
return new TestServer(builder); |
||||
|
} |
||||
|
|
||||
|
public class OpenIddictApplication { } |
||||
|
public class OpenIddictAuthorization { } |
||||
|
public class OpenIddictScope { } |
||||
|
public class OpenIddictToken { } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,190 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System; |
||||
|
using AspNet.Security.OAuth.Validation; |
||||
|
using Microsoft.AspNetCore.DataProtection; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace OpenIddict.Validation.Tests |
||||
|
{ |
||||
|
public class OpenIddictValidationBuilderTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Configure_OptionsAreCorrectlyAmended() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.Configure(configuration => configuration.ClaimsIssuer = "custom_issuer"); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal("custom_issuer", options.ClaimsIssuer); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void AddAudiences_AudiencesAreAdded() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.AddAudiences("Fabrikam", "Contoso"); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(new[] { "Fabrikam", "Contoso" }, options.Audiences); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void RegisterEvents_EventsAreAttached() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.RegisterEvents(new OAuthValidationEvents()); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.NotNull(options.ApplicationEvents); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void RegisterEvents_ThrowsAnExceptionForInvalidEventsType() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act and assert
|
||||
|
var exception = Assert.Throws<ArgumentException>(delegate |
||||
|
{ |
||||
|
return builder.RegisterEvents(typeof(object)); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal("type", exception.ParamName); |
||||
|
Assert.StartsWith("The specified type is invalid.", exception.Message); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void RegisterEvents_EventsTypeIsAttached() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.RegisterEvents(typeof(OAuthValidationEvents)); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal(typeof(OAuthValidationEvents), options.ApplicationEventsType); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void RegisterEvents_EventsAreRegistered() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.RegisterEvents(typeof(OAuthValidationEvents)); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Contains(services, service => service.ServiceType == typeof(OAuthValidationEvents)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void RemoveErrorDetails_IncludeErrorDetailsIsSetToFalse() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.RemoveErrorDetails(); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.False(options.IncludeErrorDetails); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetRealm_RealmIsReplaced() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.SetRealm("custom_realm"); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.Equal("custom_realm", options.Realm); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseDataProtectionProvider_DefaultProviderIsReplaced() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.IsType<EphemeralDataProtectionProvider>(options.DataProtectionProvider); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void UseReferenceTokens_ReferenceTokensAreEnabled() |
||||
|
{ |
||||
|
// Arrange
|
||||
|
var services = CreateServices(); |
||||
|
var builder = CreateBuilder(services); |
||||
|
|
||||
|
// Act
|
||||
|
builder.UseReferenceTokens(); |
||||
|
|
||||
|
var options = GetOptions(services); |
||||
|
|
||||
|
// Assert
|
||||
|
Assert.True(options.UseReferenceTokens); |
||||
|
} |
||||
|
|
||||
|
private static IServiceCollection CreateServices() |
||||
|
=> new ServiceCollection().AddOptions(); |
||||
|
|
||||
|
private static OpenIddictValidationBuilder CreateBuilder(IServiceCollection services) |
||||
|
=> new OpenIddictValidationBuilder(services); |
||||
|
|
||||
|
private static OpenIddictValidationOptions GetOptions(IServiceCollection services) |
||||
|
{ |
||||
|
var provider = services.BuildServiceProvider(); |
||||
|
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictValidationOptions>>(); |
||||
|
return options.Get(OpenIddictValidationDefaults.AuthenticationScheme); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,837 +0,0 @@ |
|||||
/* |
|
||||
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
||||
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|
||||
* the license and the contributors participating to this project. |
|
||||
*/ |
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Net; |
|
||||
using System.Net.Http; |
|
||||
using System.Net.Http.Headers; |
|
||||
using System.Security.Claims; |
|
||||
using System.Threading.Tasks; |
|
||||
using AspNet.Security.OAuth.Validation; |
|
||||
using Microsoft.AspNetCore.Authentication; |
|
||||
using Microsoft.AspNetCore.Builder; |
|
||||
using Microsoft.AspNetCore.DataProtection; |
|
||||
using Microsoft.AspNetCore.Hosting; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
using Microsoft.AspNetCore.TestHost; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Moq; |
|
||||
using Newtonsoft.Json; |
|
||||
using Newtonsoft.Json.Linq; |
|
||||
using OpenIddict.Abstractions; |
|
||||
using OpenIddict.Core; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace OpenIddict.Validation.Tests |
|
||||
{ |
|
||||
public class OpenIddictValidationHandlerTests |
|
||||
{ |
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_InvalidTokenCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(); |
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_ValidTokenAllowsSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(); |
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_MissingAudienceCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.AddAudiences("http://www.fabrikam.com/"); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_InvalidAudienceCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.AddAudiences("http://www.fabrikam.com/"); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token-with-single-audience"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_ValidAudienceAllowsSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.AddAudiences("http://www.fabrikam.com/"); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token-with-multiple-audiences"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_AnyMatchingAudienceCausesSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.AddAudiences("http://www.contoso.com/"); |
|
||||
builder.AddAudiences("http://www.fabrikam.com/"); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token-with-single-audience"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_MultipleMatchingAudienceCausesSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.AddAudiences("http://www.contoso.com/"); |
|
||||
builder.AddAudiences("http://www.fabrikam.com/"); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token-with-multiple-audiences"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_ExpiredTicketCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(); |
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "expired-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_AuthenticationTicketContainsRequiredClaims() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(); |
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/ticket"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token-with-scopes"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
var ticket = JObject.Parse(await response.Content.ReadAsStringAsync()); |
|
||||
var claims = from claim in ticket.Value<JArray>("Claims") |
|
||||
select new |
|
||||
{ |
|
||||
Type = claim.Value<string>(nameof(Claim.Type)), |
|
||||
Value = claim.Value<string>(nameof(Claim.Value)) |
|
||||
}; |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
|
|
||||
Assert.Contains(claims, claim => claim.Type == OAuthValidationConstants.Claims.Subject && |
|
||||
claim.Value == "Fabrikam"); |
|
||||
|
|
||||
Assert.Contains(claims, claim => claim.Type == OAuthValidationConstants.Claims.Scope && |
|
||||
claim.Value == "C54A8F5E-0387-43F4-BA43-FD4B50DC190D"); |
|
||||
|
|
||||
Assert.Contains(claims, claim => claim.Type == OAuthValidationConstants.Claims.Scope && |
|
||||
claim.Value == "5C57E3BD-9EFB-4224-9AB8-C8C5E009FFD7"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_AuthenticationTicketContainsRequiredProperties() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.SaveToken = true); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/ticket"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
var ticket = JObject.Parse(await response.Content.ReadAsStringAsync()); |
|
||||
var properties = from claim in ticket.Value<JArray>("Properties") |
|
||||
select new |
|
||||
{ |
|
||||
Name = claim.Value<string>("Name"), |
|
||||
Value = claim.Value<string>("Value") |
|
||||
}; |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
|
|
||||
Assert.Contains(properties, property => property.Name == ".Token.access_token" && |
|
||||
property.Value == "valid-token"); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_InvalidReplacedTokenCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnRetrieveToken = context => |
|
||||
{ |
|
||||
context.Token = "invalid-token"; |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_ValidReplacedTokenCausesSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnRetrieveToken = context => |
|
||||
{ |
|
||||
context.Token = "valid-token"; |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_FailFromReceiveTokenCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnRetrieveToken = context => |
|
||||
{ |
|
||||
context.Fail(new Exception()); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_NoResultFromReceiveTokenCauseInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnRetrieveToken = context => |
|
||||
{ |
|
||||
context.NoResult(); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_SuccessFromReceiveTokenCauseSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnRetrieveToken = context => |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
context.Principal = new ClaimsPrincipal(identity); |
|
||||
context.Success(); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Fabrikam", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_FailFromValidateTokenCausesInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnValidateToken = context => |
|
||||
{ |
|
||||
context.Fail(new Exception()); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_NoResultFromValidateTokenCauseInvalidAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnValidateToken = context => |
|
||||
{ |
|
||||
context.NoResult(); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleAuthenticateAsync_SuccessFromValidateTokenCauseSuccessfulAuthentication() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnValidateToken = context => |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Contoso")); |
|
||||
|
|
||||
context.Principal = new ClaimsPrincipal(identity); |
|
||||
context.Success(); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "valid-token"); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Equal("Contoso", await response.Content.ReadAsStringAsync()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleUnauthorizedAsync_ErrorDetailsAreResolvedFromChallengeContext() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.RemoveErrorDetails(); |
|
||||
builder.SetRealm("global_realm"); |
|
||||
|
|
||||
builder.Configure(options => options.Events.OnApplyChallenge = context => |
|
||||
{ |
|
||||
// Assert
|
|
||||
Assert.Equal("custom_error", context.Error); |
|
||||
Assert.Equal("custom_error_description", context.ErrorDescription); |
|
||||
Assert.Equal("custom_error_uri", context.ErrorUri); |
|
||||
Assert.Equal("custom_realm", context.Realm); |
|
||||
Assert.Equal("custom_scope", context.Scope); |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.GetAsync("/challenge"); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
Assert.Equal(@"Bearer realm=""custom_realm"", error=""custom_error"", error_description=""custom_error_description"", " + |
|
||||
@"error_uri=""custom_error_uri"", scope=""custom_scope""", response.Headers.WwwAuthenticate.ToString()); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData("invalid-token", OAuthValidationConstants.Errors.InvalidToken, "The access token is not valid.")] |
|
||||
[InlineData("expired-token", OAuthValidationConstants.Errors.InvalidToken, "The access token is no longer valid.")] |
|
||||
public async Task HandleUnauthorizedAsync_ErrorDetailsAreInferredFromAuthenticationFailure( |
|
||||
string token, string error, string description) |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(); |
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/"); |
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.SendAsync(request); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
Assert.Equal($@"Bearer error=""{error}"", error_description=""{description}""", |
|
||||
response.Headers.WwwAuthenticate.ToString()); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task HandleUnauthorizedAsync_ApplyChallenge_AllowsHandlingResponse() |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnApplyChallenge = context => |
|
||||
{ |
|
||||
context.HandleResponse(); |
|
||||
context.HttpContext.Response.Headers["X-Custom-Authentication-Header"] = "Bearer"; |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.GetAsync("/challenge"); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode); |
|
||||
Assert.Empty(response.Headers.WwwAuthenticate); |
|
||||
Assert.Equal(new[] { "Bearer" }, response.Headers.GetValues("X-Custom-Authentication-Header")); |
|
||||
} |
|
||||
|
|
||||
[Theory] |
|
||||
[InlineData(null, null, null, null, null, "Bearer")] |
|
||||
[InlineData("custom_error", null, null, null, null, @"Bearer error=""custom_error""")] |
|
||||
[InlineData(null, "custom_error_description", null, null, null, @"Bearer error_description=""custom_error_description""")] |
|
||||
[InlineData(null, null, "custom_error_uri", null, null, @"Bearer error_uri=""custom_error_uri""")] |
|
||||
[InlineData(null, null, null, "custom_realm", null, @"Bearer realm=""custom_realm""")] |
|
||||
[InlineData(null, null, null, null, "custom_scope", @"Bearer scope=""custom_scope""")] |
|
||||
[InlineData("custom_error", "custom_error_description", "custom_error_uri", "custom_realm", "custom_scope", |
|
||||
@"Bearer realm=""custom_realm"", error=""custom_error"", " + |
|
||||
@"error_description=""custom_error_description"", " + |
|
||||
@"error_uri=""custom_error_uri"", scope=""custom_scope""")] |
|
||||
public async Task HandleUnauthorizedAsync_ReturnsExpectedWwwAuthenticateHeader( |
|
||||
string error, string description, string uri, string realm, string scope, string header) |
|
||||
{ |
|
||||
// Arrange
|
|
||||
var server = CreateResourceServer(builder => |
|
||||
{ |
|
||||
builder.Configure(options => options.Events.OnApplyChallenge = context => |
|
||||
{ |
|
||||
context.Error = error; |
|
||||
context.ErrorDescription = description; |
|
||||
context.ErrorUri = uri; |
|
||||
context.Realm = realm; |
|
||||
context.Scope = scope; |
|
||||
|
|
||||
return Task.FromResult(0); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
var client = server.CreateClient(); |
|
||||
|
|
||||
// Act
|
|
||||
var response = await client.GetAsync("/challenge"); |
|
||||
|
|
||||
// Assert
|
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); |
|
||||
Assert.Equal(header, response.Headers.WwwAuthenticate.ToString()); |
|
||||
} |
|
||||
|
|
||||
private static TestServer CreateResourceServer(Action<OpenIddictValidationBuilder> configuration = null) |
|
||||
{ |
|
||||
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>(MockBehavior.Strict); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "invalid-token"))) |
|
||||
.Returns(value: null); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "valid-token"))) |
|
||||
.Returns(delegate |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
var properties = new AuthenticationProperties(); |
|
||||
|
|
||||
return new AuthenticationTicket(new ClaimsPrincipal(identity), |
|
||||
properties, OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
}); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "valid-token-with-scopes"))) |
|
||||
.Returns(delegate |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
var properties = new AuthenticationProperties(); |
|
||||
properties.Items[OAuthValidationConstants.Properties.Scopes] = |
|
||||
@"[""C54A8F5E-0387-43F4-BA43-FD4B50DC190D"",""5C57E3BD-9EFB-4224-9AB8-C8C5E009FFD7""]"; |
|
||||
|
|
||||
return new AuthenticationTicket(new ClaimsPrincipal(identity), |
|
||||
properties, OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
}); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "valid-token-with-single-audience"))) |
|
||||
.Returns(delegate |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|
||||
{ |
|
||||
[OAuthValidationConstants.Properties.Audiences] = @"[""http://www.contoso.com/""]" |
|
||||
}); |
|
||||
|
|
||||
return new AuthenticationTicket(new ClaimsPrincipal(identity), |
|
||||
properties, OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
}); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "valid-token-with-multiple-audiences"))) |
|
||||
.Returns(delegate |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|
||||
{ |
|
||||
[OAuthValidationConstants.Properties.Audiences] = @"[""http://www.contoso.com/"",""http://www.fabrikam.com/""]" |
|
||||
}); |
|
||||
|
|
||||
return new AuthenticationTicket(new ClaimsPrincipal(identity), |
|
||||
properties, OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
}); |
|
||||
|
|
||||
format.Setup(mock => mock.Unprotect(It.Is<string>(token => token == "expired-token"))) |
|
||||
.Returns(delegate |
|
||||
{ |
|
||||
var identity = new ClaimsIdentity(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
identity.AddClaim(new Claim(OAuthValidationConstants.Claims.Subject, "Fabrikam")); |
|
||||
|
|
||||
var properties = new AuthenticationProperties(); |
|
||||
properties.ExpiresUtc = DateTimeOffset.UtcNow - TimeSpan.FromDays(1); |
|
||||
|
|
||||
return new AuthenticationTicket(new ClaimsPrincipal(identity), |
|
||||
properties, OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
}); |
|
||||
|
|
||||
var builder = new WebHostBuilder(); |
|
||||
builder.UseEnvironment("Testing"); |
|
||||
|
|
||||
builder.ConfigureLogging(options => options.AddDebug()); |
|
||||
|
|
||||
builder.ConfigureServices(services => |
|
||||
{ |
|
||||
services.AddOpenIddict() |
|
||||
.AddCore(options => |
|
||||
{ |
|
||||
options.SetDefaultApplicationEntity<OpenIddictApplication>() |
|
||||
.SetDefaultAuthorizationEntity<OpenIddictAuthorization>() |
|
||||
.SetDefaultScopeEntity<OpenIddictScope>() |
|
||||
.SetDefaultTokenEntity<OpenIddictToken>(); |
|
||||
|
|
||||
// Replace the default OpenIddict managers.
|
|
||||
options.Services.AddSingleton(CreateApplicationManager()); |
|
||||
options.Services.AddSingleton(CreateAuthorizationManager()); |
|
||||
options.Services.AddSingleton(CreateScopeManager()); |
|
||||
options.Services.AddSingleton(CreateTokenManager()); |
|
||||
}) |
|
||||
|
|
||||
.AddValidation(options => |
|
||||
{ |
|
||||
options.Configure(settings => settings.AccessTokenFormat = format.Object); |
|
||||
|
|
||||
// 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.
|
|
||||
options.UseDataProtectionProvider(new EphemeralDataProtectionProvider()); |
|
||||
|
|
||||
// Run the configuration delegate
|
|
||||
// registered by the unit tests.
|
|
||||
configuration?.Invoke(options); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
builder.Configure(app => |
|
||||
{ |
|
||||
app.Map("/ticket", map => map.Run(async context => |
|
||||
{ |
|
||||
var result = await context.AuthenticateAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
if (result.Principal == null) |
|
||||
{ |
|
||||
await context.ChallengeAsync(); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
context.Response.ContentType = "application/json"; |
|
||||
|
|
||||
// Return the authentication ticket as a JSON object.
|
|
||||
await context.Response.WriteAsync(JsonConvert.SerializeObject(new |
|
||||
{ |
|
||||
Claims = from claim in result.Principal.Claims |
|
||||
select new { claim.Type, claim.Value }, |
|
||||
|
|
||||
Properties = from property in result.Properties.Items |
|
||||
select new { Name = property.Key, property.Value } |
|
||||
})); |
|
||||
})); |
|
||||
|
|
||||
app.Map("/challenge", map => map.Run(context => |
|
||||
{ |
|
||||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|
||||
{ |
|
||||
[OAuthValidationConstants.Properties.Error] = "custom_error", |
|
||||
[OAuthValidationConstants.Properties.ErrorDescription] = "custom_error_description", |
|
||||
[OAuthValidationConstants.Properties.ErrorUri] = "custom_error_uri", |
|
||||
[OAuthValidationConstants.Properties.Realm] = "custom_realm", |
|
||||
[OAuthValidationConstants.Properties.Scope] = "custom_scope", |
|
||||
}); |
|
||||
|
|
||||
return context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme, properties); |
|
||||
})); |
|
||||
|
|
||||
app.Run(async context => |
|
||||
{ |
|
||||
var result = await context.AuthenticateAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
if (result.Principal == null) |
|
||||
{ |
|
||||
await context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
var subject = result.Principal.FindFirst(OAuthValidationConstants.Claims.Subject)?.Value; |
|
||||
if (string.IsNullOrEmpty(subject)) |
|
||||
{ |
|
||||
await context.ChallengeAsync(OpenIddictValidationDefaults.AuthenticationScheme); |
|
||||
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
await context.Response.WriteAsync(subject); |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
return new TestServer(builder); |
|
||||
} |
|
||||
|
|
||||
private static OpenIddictApplicationManager<OpenIddictApplication> CreateApplicationManager( |
|
||||
Action<Mock<OpenIddictApplicationManager<OpenIddictApplication>>> configuration = null) |
|
||||
{ |
|
||||
var manager = new Mock<OpenIddictApplicationManager<OpenIddictApplication>>( |
|
||||
Mock.Of<IOpenIddictApplicationStoreResolver>(), |
|
||||
Mock.Of<ILogger<OpenIddictApplicationManager<OpenIddictApplication>>>(), |
|
||||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|
||||
|
|
||||
configuration?.Invoke(manager); |
|
||||
|
|
||||
return manager.Object; |
|
||||
} |
|
||||
|
|
||||
private static OpenIddictAuthorizationManager<OpenIddictAuthorization> CreateAuthorizationManager( |
|
||||
Action<Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>> configuration = null) |
|
||||
{ |
|
||||
var manager = new Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>( |
|
||||
Mock.Of<IOpenIddictAuthorizationStoreResolver>(), |
|
||||
Mock.Of<ILogger<OpenIddictAuthorizationManager<OpenIddictAuthorization>>>(), |
|
||||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|
||||
|
|
||||
configuration?.Invoke(manager); |
|
||||
|
|
||||
return manager.Object; |
|
||||
} |
|
||||
|
|
||||
private static OpenIddictScopeManager<OpenIddictScope> CreateScopeManager( |
|
||||
Action<Mock<OpenIddictScopeManager<OpenIddictScope>>> configuration = null) |
|
||||
{ |
|
||||
var manager = new Mock<OpenIddictScopeManager<OpenIddictScope>>( |
|
||||
Mock.Of<IOpenIddictScopeStoreResolver>(), |
|
||||
Mock.Of<ILogger<OpenIddictScopeManager<OpenIddictScope>>>(), |
|
||||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|
||||
|
|
||||
configuration?.Invoke(manager); |
|
||||
|
|
||||
return manager.Object; |
|
||||
} |
|
||||
|
|
||||
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager( |
|
||||
Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> configuration = null) |
|
||||
{ |
|
||||
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>( |
|
||||
Mock.Of<IOpenIddictTokenStoreResolver>(), |
|
||||
Mock.Of<ILogger<OpenIddictTokenManager<OpenIddictToken>>>(), |
|
||||
Mock.Of<IOptionsMonitor<OpenIddictCoreOptions>>()); |
|
||||
|
|
||||
configuration?.Invoke(manager); |
|
||||
|
|
||||
return manager.Object; |
|
||||
} |
|
||||
|
|
||||
public class OpenIddictApplication { } |
|
||||
public class OpenIddictAuthorization { } |
|
||||
public class OpenIddictScope { } |
|
||||
public class OpenIddictToken { } |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue