/* * 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.Linq; using System.Net.Http; using System.Security.Claims; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.JsonWebTokens; using Moq; using OpenIddict.Abstractions; using Xunit; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers.Introspection; namespace OpenIddict.Server.FunctionalTests { public abstract partial class OpenIddictServerIntegrationTests { [Theory] [InlineData(nameof(HttpMethod.Delete))] [InlineData(nameof(HttpMethod.Head))] [InlineData(nameof(HttpMethod.Options))] [InlineData(nameof(HttpMethod.Put))] [InlineData(nameof(HttpMethod.Trace))] public async Task ExtractIntrospectionRequest_UnexpectedMethodReturnsAnError(string method) { // Arrange var client = CreateClient(options => options.EnableDegradedMode()); // Act var response = await client.SendAsync(method, "/connect/introspect", new OpenIddictRequest()); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal("The specified HTTP method is not valid.", response.ErrorDescription); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task ExtractIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest()); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task ExtractIntrospectionRequest_AllowsHandlingResponse() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); // Act var response = await client.GetAsync("/connect/introspect"); // Assert Assert.Equal("Bob le Bricoleur", (string) response["name"]); } [Fact] public async Task ExtractIntrospectionRequest_AllowsSkippingHandler() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); // Act var response = await client.GetAsync("/connect/introspect"); // Assert Assert.Equal("Bob le Magnifique", (string) response["name"]); } [Fact] public async Task ValidateIntrospectionRequest_MissingTokenCausesAnError() { // Arrange var client = CreateClient(options => options.EnableDegradedMode()); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = null }); // Assert Assert.Equal(Errors.InvalidRequest, response.Error); Assert.Equal("The mandatory 'token' parameter is missing.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_InvalidTokenCausesAnError() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The specified token is invalid.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_ExpiredTokenCausesAnError() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1)); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The specified token is no longer valid.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_AuthorizationCodeCausesAnErrorWhenPresentersAreMissing() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters(Enumerable.Empty()); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); }); // Act and assert var exception = await Assert.ThrowsAsync(delegate { return client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.AuthorizationCode }); }); Assert.Equal("The presenters list cannot be extracted from the authorization code.", exception.Message); } [Fact] public async Task ValidateIntrospectionRequest_AuthorizationCodeCausesAnErrorWhenCallerIsNotAValidPresenter() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("SlAV32hkKG", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Contoso"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "SlAV32hkKG", TokenTypeHint = TokenTypeHints.AuthorizationCode }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The client application is not allowed to introspect the specified token.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("AdventureWorks") .SetPresenters("Contoso"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The client application is not allowed to introspect the specified token.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_IdentityTokenCausesAnErrorWhenCallerIsNotAValidAudience() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.IdToken) .SetAudiences("AdventureWorks"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.IdToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The client application is not allowed to introspect the specified token.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("8xLOxBtZp8", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Contoso"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "8xLOxBtZp8", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The client application is not allowed to introspect the specified token.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired() { // Arrange var client = CreateClient(builder => { builder.Configure(options => options.AcceptAnonymousClients = false); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal("The mandatory 'client_id' parameter is missing.", response.ErrorDescription); } [Fact] public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound() { // Arrange var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(value: null); }); var client = CreateClient(options => { options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.InvalidClient, response.Error); Assert.Equal("The specified 'client_id' parameter is invalid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.Once()); } [Fact] public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Public); mock.Setup(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Introspection, It.IsAny())) .ReturnsAsync(false); }); var client = CreateClient(options => { options.Services.AddSingleton(manager); options.Configure(options => options.IgnoreEndpointPermissions = false); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(Errors.UnauthorizedClient, response.Error); Assert.Equal("This client application is not allowed to use the introspection endpoint.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application, Permissions.Endpoints.Introspection, It.IsAny()), Times.Once()); } [Fact] public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(false); }); var client = CreateClient(options => { options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.GetClientTypeAsync(application, It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny()), Times.Once()); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task ValidateIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task ValidateIntrospectionRequest_AllowsHandlingResponse() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string) response["name"]); } [Fact] public async Task ValidateIntrospectionRequest_AllowsSkippingHandler() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Magnifique", (string) response["name"]); } [Fact] public async Task HandleIntrospectionRequest_BasicClaimsAreCorrectlyReturned() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("Fabrikam") .SetPresenters("Contoso", "AdventureWorks Cycles") .SetCreationDate(new DateTimeOffset(2016, 1, 1, 0, 0, 0, TimeSpan.Zero)) .SetExpirationDate(new DateTimeOffset(2017, 1, 1, 0, 0, 0, TimeSpan.Zero)) .SetClaim(Claims.Subject, "Bob le Magnifique") .SetClaim(Claims.JwtId, "66B65AED-4033-4E9C-B975-A8CA7FB6FA79"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.RemoveEventHandler(ValidateExpirationDate.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.Equal(11, response.GetParameters().Count()); Assert.True((bool) response[Claims.Active]); Assert.Equal("66B65AED-4033-4E9C-B975-A8CA7FB6FA79", (string) response[Claims.JwtId]); Assert.Equal(TokenTypes.Bearer, (string) response[Claims.TokenType]); Assert.Equal(TokenTypeHints.AccessToken, (string) response[Claims.TokenUsage]); Assert.Equal("http://localhost/", (string) response[Claims.Issuer]); Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]); Assert.Equal(1451606400, (long) response[Claims.IssuedAt]); Assert.Equal(1451606400, (long) response[Claims.NotBefore]); Assert.Equal(1483228800, (long) response[Claims.ExpiresAt]); Assert.Equal("Fabrikam", (string) response[Claims.Audience]); Assert.Equal("Contoso", (string) response[Claims.ClientId]); } [Fact] public async Task HandleIntrospectionRequest_NonBasicAuthorizationCodeClaimsAreNotReturned() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AuthorizationCode) .SetPresenters("Fabrikam") .SetClaim(Claims.Username, "Bob") .SetClaim("custom_claim", "secret_value"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AuthorizationCode }); // Assert Assert.Null(response["custom_claim"]); Assert.Null(response[Claims.Username]); } [Fact] public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.RefreshToken) .SetPresenters("Fabrikam") .SetClaim(Claims.Username, "Bob") .SetClaim("custom_claim", "secret_value"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.RefreshToken }); // Assert Assert.Null(response["custom_claim"]); Assert.Null(response[Claims.Username]); } [Fact] public async Task HandleIntrospectionRequest_NonBasicAccessTokenClaimsAreReturnedToTrustedAudiences() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("Fabrikam") .SetPresenters("Contoso", "AdventureWorks Cycles") .SetScopes(Scopes.OpenId, Scopes.Profile) .SetClaim(Claims.Username, "Bob") .SetClaim("custom_claim", "secret_value"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.Equal("secret_value", (string) response["custom_claim"]); Assert.Equal("Bob", (string) response[Claims.Username]); Assert.Equal("openid profile", (string) response[Claims.Scope]); } [Fact] public async Task HandleIntrospectionRequest_NonBasicIdentityClaimsAreReturnedToTrustedAudiences() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.IdToken) .SetAudiences("Fabrikam") .SetClaim(Claims.Username, "Bob") .SetClaim("custom_claim", "secret_value"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.IdToken }); // Assert Assert.Equal("secret_value", (string) response["custom_claim"]); Assert.Equal("Bob", (string) response[Claims.Username]); } [Fact] public async Task HandleIntrospectionRequest_ClaimValueTypesAreHonored() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); var identity = new ClaimsIdentity("Bearer"); identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); identity.AddClaim(new Claim("object_claim", @"{""parameter"":""value""}", JsonClaimValueTypes.Json)); context.Principal = new ClaimsPrincipal(identity) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("Fabrikam"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.True((bool) response["boolean_claim"]); Assert.Equal(JsonValueKind.True, ((JsonElement) response["boolean_claim"]).ValueKind); Assert.Equal(42, (long) response["integer_claim"]); Assert.Equal(JsonValueKind.Number, ((JsonElement) response["integer_claim"]).ValueKind); Assert.Equal(new[] { "Contoso", "Fabrikam" }, (string[]) response["array_claim"]); Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); Assert.Equal("value", (string) response["object_claim"]?["parameter"]); Assert.Equal(JsonValueKind.Object, ((JsonElement) response["object_claim"]).ValueKind); } [Fact] public async Task HandleIntrospectionRequest_MultipleClaimsAreReturnedAsArrays() { // Arrange var application = new OpenIddictApplication(); var manager = CreateApplicationManager(mock => { mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); var identity = new ClaimsIdentity("Bearer"); identity.AddClaim(new Claim("boolean_claim", "true", ClaimValueTypes.Boolean)); identity.AddClaim(new Claim("boolean_claim", "false", ClaimValueTypes.Boolean)); identity.AddClaim(new Claim("integer_claim", "42", ClaimValueTypes.Integer)); identity.AddClaim(new Claim("integer_claim", "43", ClaimValueTypes.Integer)); identity.AddClaim(new Claim("array_claim", @"[""Contoso"",""Fabrikam""]", JsonClaimValueTypes.JsonArray)); identity.AddClaim(new Claim("array_claim", @"[""Microsoft"",""Google""]", JsonClaimValueTypes.JsonArray)); identity.AddClaim(new Claim("object_claim", @"{""parameter_1"":""value-1""}", JsonClaimValueTypes.Json)); identity.AddClaim(new Claim("object_claim", @"{""parameter_2"":""value-2""}", JsonClaimValueTypes.Json)); context.Principal = new ClaimsPrincipal(identity) .SetTokenType(TokenTypeHints.AccessToken) .SetAudiences("Fabrikam"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(manager); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "2YotnFZFEjr1zCsicMWpAA", TokenTypeHint = TokenTypeHints.AccessToken }); // Assert Assert.Equal(JsonValueKind.Array, ((JsonElement) response["boolean_claim"]).ValueKind); Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); Assert.True(((JsonElement) response["boolean_claim"])[0].GetBoolean()); Assert.False(((JsonElement) response["boolean_claim"])[1].GetBoolean()); Assert.Equal(JsonValueKind.Array, ((JsonElement) response["integer_claim"]).ValueKind); Assert.Equal(2, ((JsonElement) response["boolean_claim"]).GetArrayLength()); Assert.Equal(42, ((JsonElement) response["integer_claim"])[0].GetInt64()); Assert.Equal(43, ((JsonElement) response["integer_claim"])[1].GetInt64()); Assert.Equal(JsonValueKind.Array, ((JsonElement) response["array_claim"]).ValueKind); Assert.Equal(2, ((JsonElement) response["array_claim"]).GetArrayLength()); Assert.Equal(2, ((JsonElement) response["array_claim"])[0].GetArrayLength()); Assert.Equal("Contoso", ((JsonElement) response["array_claim"])[0][0].GetString()); Assert.Equal("Fabrikam", ((JsonElement) response["array_claim"])[0][1].GetString()); Assert.Equal(2, ((JsonElement) response["array_claim"])[1].GetArrayLength()); Assert.Equal("Microsoft", ((JsonElement) response["array_claim"])[1][0].GetString()); Assert.Equal("Google", ((JsonElement) response["array_claim"])[1][1].GetString()); Assert.Equal(JsonValueKind.Array, ((JsonElement) response["object_claim"]).ValueKind); Assert.Equal(2, ((JsonElement) response["object_claim"]).GetArrayLength()); Assert.Equal("value-1", ((JsonElement) response["object_claim"])[0].GetProperty("parameter_1").GetString()); Assert.Equal("value-2", ((JsonElement) response["object_claim"])[1].GetProperty("parameter_2").GetString()); } [Fact] public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsUnknown() { // Arrange var manager = CreateTokenManager(mock => { mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(value: null); }); var client = CreateClient(options => { options.UseReferenceAccessTokens(); options.Services.AddSingleton(CreateApplicationManager(mock => { var application = new OpenIddictApplication(); mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); options.Services.AddSingleton(manager); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The specified token is invalid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.AtLeastOnce()); } [Fact] public async Task HandleIntrospectionRequest_AuthorizationIsIgnoredWhenAuthorizationStorageIsDisabled() { // Arrange var manager = CreateAuthorizationManager(mock => { mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) .ReturnsAsync(new OpenIddictAuthorization()); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetAudiences("Fabrikam") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(CreateApplicationManager(mock => { var application = new OpenIddictApplication(); mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => { var token = new OpenIddictToken(); mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.AccessToken); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); })); options.Services.AddSingleton(manager); options.DisableAuthorizationStorage(); options.UseReferenceAccessTokens(); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Equal("Bob le Magnifique", (string) response[Claims.Subject]); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Never()); } [Fact] public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationCannotBeFound() { // Arrange var manager = CreateAuthorizationManager(mock => { mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) .ReturnsAsync(value: null); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetAudiences("Fabrikam") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(CreateApplicationManager(mock => { var application = new OpenIddictApplication(); mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => { var token = new OpenIddictToken(); mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.AccessToken); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); })); options.Services.AddSingleton(manager); options.UseReferenceAccessTokens(); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The authorization associated with the token is no longer valid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); } [Fact] public async Task HandleIntrospectionRequest_RequestIsRejectedWhenAuthorizationIsInvalid() { // Arrange var authorization = new OpenIddictAuthorization(); var manager = CreateAuthorizationManager(mock => { mock.Setup(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny())) .ReturnsAsync(authorization); mock.Setup(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny())) .ReturnsAsync(false); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetAudiences("Fabrikam") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(CreateApplicationManager(mock => { var application = new OpenIddictApplication(); mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); options.Services.AddSingleton(CreateTokenManager(mock => { var token = new OpenIddictToken(); mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.AccessToken); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); })); options.Services.AddSingleton(manager); options.UseReferenceAccessTokens(); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The authorization associated with the token is no longer valid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny()), Times.Once()); Mock.Get(manager).Verify(manager => manager.HasStatusAsync(authorization, Statuses.Valid, It.IsAny()), Times.Once()); } [Fact] public async Task HandleIntrospectionRequest_RequestIsRejectedWhenReferenceTokenIsInvalid() { // Arrange var token = new OpenIddictToken(); var manager = CreateTokenManager(mock => { mock.Setup(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) .ReturnsAsync(TokenTypeHints.AccessToken); mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); mock.Setup(manager => manager.GetPayloadAsync(token, It.IsAny())) .ReturnsAsync("2YotnFZFEjr1zCsicMWpAA"); mock.Setup(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny())) .ReturnsAsync(token); mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(false); }); var client = CreateClient(options => { options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetAudiences("Fabrikam") .SetInternalAuthorizationId("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0") .SetInternalTokenId("3E228451-1555-46F7-A471-951EFBA23A56") .SetTokenType(TokenTypeHints.AccessToken) .SetClaim(Claims.Subject, "Bob le Magnifique"); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.Services.AddSingleton(CreateApplicationManager(mock => { var application = new OpenIddictApplication(); mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny())) .ReturnsAsync(application); mock.Setup(manager => manager.GetClientTypeAsync(application, It.IsAny())) .ReturnsAsync(ClientTypes.Confidential); mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny())) .ReturnsAsync(true); })); options.Services.AddSingleton(manager); options.UseReferenceAccessTokens(); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { ClientId = "Fabrikam", ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw", Token = "QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI" }); // Assert Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal("The specified token is no longer valid.", response.ErrorDescription); Mock.Get(manager).Verify(manager => manager.FindByReferenceIdAsync("QaTk2f6UPe9trKismGBJr0OIs0KqpvNrqRsJqGuJAAI", It.IsAny()), Times.Once()); Mock.Get(manager).Verify(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()), Times.Once()); } [Theory] [InlineData("custom_error", null, null)] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, null, "custom_uri")] [InlineData(null, null, null)] public async Task HandleIntrospectionRequest_AllowsRejectingRequest(string error, string description, string uri) { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Reject(error, description, uri); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal(error ?? Errors.InvalidRequest, response.Error); Assert.Equal(description, response.ErrorDescription); Assert.Equal(uri, response.ErrorUri); } [Fact] public async Task HandleIntrospectionRequest_AllowsHandlingResponse() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string) response["name"]); } [Fact] public async Task HandleIntrospectionRequest_AllowsSkippingHandler() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.SkipRequest(); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Magnifique", (string) response["name"]); } [Fact] public async Task ApplyIntrospectionResponse_AllowsHandlingResponse() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => { builder.UseInlineHandler(context => { Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) .SetTokenType(TokenTypeHints.AccessToken); return default; }); builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500); }); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Transaction.SetProperty("custom_response", new { name = "Bob le Bricoleur" }); context.HandleRequest(); return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "2YotnFZFEjr1zCsicMWpAA" }); // Assert Assert.Equal("Bob le Bricoleur", (string) response["name"]); } [Fact] public async Task ApplyIntrospectionResponse_ResponseContainsCustomParameters() { // Arrange var client = CreateClient(options => { options.EnableDegradedMode(); options.AddEventHandler(builder => builder.UseInlineHandler(context => { context.Response["custom_parameter"] = "custom_value"; context.Response["parameter_with_multiple_values"] = new[] { "custom_value_1", "custom_value_2" }; return default; })); }); // Act var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest { Token = "SlAV32hkKG" }); // Assert Assert.Equal("custom_value", (string) response["custom_parameter"]); Assert.Equal(new[] { "custom_value_1", "custom_value_2" }, (string[]) response["parameter_with_multiple_values"]); } } }