diff --git a/source/AccessTokenValidation.Tests/AccessTokenValidation.Tests.csproj b/source/AccessTokenValidation.Tests/AccessTokenValidation.Tests.csproj index d803a4f..b4cc8d9 100644 --- a/source/AccessTokenValidation.Tests/AccessTokenValidation.Tests.csproj +++ b/source/AccessTokenValidation.Tests/AccessTokenValidation.Tests.csproj @@ -111,12 +111,14 @@ + + diff --git a/source/AccessTokenValidation.Tests/Integration Tests/Introspection.cs b/source/AccessTokenValidation.Tests/Integration Tests/Introspection.cs new file mode 100644 index 0000000..a4d5ca6 --- /dev/null +++ b/source/AccessTokenValidation.Tests/Integration Tests/Introspection.cs @@ -0,0 +1,57 @@ +using AccessTokenValidation.Tests.Util; +using FluentAssertions; +using IdentityServer3.AccessTokenValidation; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace AccessTokenValidation.Tests.Integration_Tests +{ + public class Introspection + { + IdentityServerBearerTokenAuthenticationOptions _options = new IdentityServerBearerTokenAuthenticationOptions + { + Authority = "https://server/with/introspection", + ValidationMode = ValidationMode.ValidationEndpoint, + ClientId = "client", + ClientSecret = "secret" + }; + + [Fact] + public async Task Unauthorized_Client() + { + _options.IntrospectionHttpHandler = new IntrospectionEndpointHandler(IntrospectionEndpointHandler.Behavior.Unauthorized); + + var client = PipelineFactory.CreateHttpClient(_options); + client.SetBearerToken("sometoken"); + + var result = await client.GetAsync("http://test"); + result.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task ActiveToken() + { + _options.IntrospectionHttpHandler = new IntrospectionEndpointHandler(IntrospectionEndpointHandler.Behavior.Active); + + var client = PipelineFactory.CreateHttpClient(_options); + client.SetBearerToken("sometoken"); + + var result = await client.GetAsync("http://test"); + result.StatusCode.Should().Be(HttpStatusCode.OK); + } + + [Fact] + public async Task InactiveToken() + { + _options.IntrospectionHttpHandler = new IntrospectionEndpointHandler(IntrospectionEndpointHandler.Behavior.Inactive); + + var client = PipelineFactory.CreateHttpClient(_options); + client.SetBearerToken("sometoken"); + + var result = await client.GetAsync("http://test"); + result.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } + } +} \ No newline at end of file diff --git a/source/AccessTokenValidation.Tests/Util/IntrospectionEndpointHandler.cs b/source/AccessTokenValidation.Tests/Util/IntrospectionEndpointHandler.cs new file mode 100644 index 0000000..b2bd48d --- /dev/null +++ b/source/AccessTokenValidation.Tests/Util/IntrospectionEndpointHandler.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Formatting; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AccessTokenValidation.Tests.Util +{ + class IntrospectionEndpointHandler : WebRequestHandler + { + private readonly Behavior _behavior; + + public enum Behavior + { + Active, + Inactive, + Unauthorized + } + + public IntrospectionEndpointHandler(Behavior behavior) + { + _behavior = behavior; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (_behavior == Behavior.Unauthorized) + { + var response = new HttpResponseMessage(HttpStatusCode.Unauthorized); + return Task.FromResult(response); + } + if (_behavior == Behavior.Active) + { + var responseObject = new Dictionary + { + { "active", true } + }; + + var response = new HttpResponseMessage(HttpStatusCode.OK); + response.Content = new ObjectContent>( + responseObject, new JsonMediaTypeFormatter()); + + return Task.FromResult(response); + } + if (_behavior == Behavior.Inactive) + { + var responseObject = new Dictionary + { + { "active", false } + }; + + var response = new HttpResponseMessage(HttpStatusCode.OK); + response.Content = new ObjectContent>( + responseObject, new JsonMediaTypeFormatter()); + + return Task.FromResult(response); + } + + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/source/AccessTokenValidation/Plumbing/IntrospectionEndpointTokenProvider.cs b/source/AccessTokenValidation/Plumbing/IntrospectionEndpointTokenProvider.cs index f7a7826..e5e6f52 100644 --- a/source/AccessTokenValidation/Plumbing/IntrospectionEndpointTokenProvider.cs +++ b/source/AccessTokenValidation/Plumbing/IntrospectionEndpointTokenProvider.cs @@ -100,6 +100,11 @@ public override async Task ReceiveAsync(AuthenticationTokenReceiveContext contex _logger.WriteError("Error returned from introspection endpoint: " + response.Error); return; } + if (!response.IsActive) + { + _logger.WriteVerbose("Inactive token: " + context.Token); + return; + } } catch (Exception ex) { @@ -110,7 +115,10 @@ public override async Task ReceiveAsync(AuthenticationTokenReceiveContext contex var claims = new List(); foreach (var claim in response.Claims) { - claims.Add(new Claim(claim.Item1, claim.Item2)); + if (!string.Equals(claim.Item1, "active", StringComparison.Ordinal)) + { + claims.Add(new Claim(claim.Item1, claim.Item2)); + } } if (_options.EnableValidationResultCache)