diff --git a/source/Core/Configuration/Hosting/AutoFacConfig.cs b/source/Core/Configuration/Hosting/AutoFacConfig.cs index fdfb45c2c..d66e268ba 100644 --- a/source/Core/Configuration/Hosting/AutoFacConfig.cs +++ b/source/Core/Configuration/Hosting/AutoFacConfig.cs @@ -153,6 +153,7 @@ public static IContainer Configure(IdentityServerOptions options) builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); + builder.RegisterType(); // for authentication var authenticationOptions = options.AuthenticationOptions ?? new AuthenticationOptions(); diff --git a/source/Core/Core.csproj b/source/Core/Core.csproj index 38e20e6df..b02bff779 100644 --- a/source/Core/Core.csproj +++ b/source/Core/Core.csproj @@ -56,7 +56,7 @@ ..\packages\Autofac.WebApi2.3.4.0\lib\net45\Autofac.Integration.WebApi.dll - ..\packages\IdentityModel.1.2.1\lib\net45\IdentityModel.Net45.dll + ..\packages\IdentityModel.1.3.0\lib\net45\IdentityModel.Net45.dll True @@ -225,6 +225,7 @@ True T4resx.tt + diff --git a/source/Core/Endpoints/Connect/IntrospectionEndpointController.cs b/source/Core/Endpoints/Connect/IntrospectionEndpointController.cs index f662474ea..f7b034431 100644 --- a/source/Core/Endpoints/Connect/IntrospectionEndpointController.cs +++ b/source/Core/Endpoints/Connect/IntrospectionEndpointController.cs @@ -19,6 +19,7 @@ using IdentityServer3.Core.Extensions; using IdentityServer3.Core.Logging; using IdentityServer3.Core.Models; +using IdentityServer3.Core.ResponseHandling; using IdentityServer3.Core.Results; using IdentityServer3.Core.Services; using IdentityServer3.Core.Validation; @@ -42,17 +43,20 @@ internal class IntrospectionEndpointController : ApiController private readonly IEventService _events; private readonly ScopeSecretValidator _scopeSecretValidator; private readonly IntrospectionRequestValidator _requestValidator; + private readonly IntrospectionResponseGenerator _generator; public IntrospectionEndpointController( IntrospectionRequestValidator requestValidator, IdentityServerOptions options, IEventService events, - ScopeSecretValidator scopeSecretValidator) + ScopeSecretValidator scopeSecretValidator, + IntrospectionResponseGenerator generator) { _requestValidator = requestValidator; _scopeSecretValidator = scopeSecretValidator; _options = options; _events = events; + _generator = generator; } /// @@ -77,11 +81,12 @@ public async Task Post() internal async Task ProcessRequest(NameValueCollection parameters, Scope scope) { var validationResult = await _requestValidator.ValidateAsync(parameters, scope); + var response = await _generator.ProcessAsync(validationResult, scope); if (validationResult.IsActive) { - await RaiseSuccessEventAsync(validationResult.Token, "active", scope.Name); - return new IntrospectionResult(validationResult, scope); + await RaiseSuccessEventAsync(validationResult.Token, "active", scope.Name); + return new IntrospectionResult(response); } if (validationResult.IsError) @@ -97,13 +102,13 @@ internal async Task ProcessRequest(NameValueCollection parame if (validationResult.FailureReason == IntrospectionRequestValidationFailureReason.InvalidToken) { await RaiseSuccessEventAsync(validationResult.Token, "inactive", scope.Name); - return new IntrospectionResult(); + return new IntrospectionResult(response); } if (validationResult.FailureReason == IntrospectionRequestValidationFailureReason.InvalidScope) { await RaiseFailureEventAsync("Scope not authorized to introspect token", validationResult.Token, scope.Name); - return new IntrospectionResult(); + return new IntrospectionResult(response); } } diff --git a/source/Core/Models/Scope.cs b/source/Core/Models/Scope.cs index 1b96540e9..cce0bc275 100644 --- a/source/Core/Models/Scope.cs +++ b/source/Core/Models/Scope.cs @@ -86,6 +86,11 @@ public class Scope /// public List ScopeSecrets { get; set; } + /// + /// Specifies whether this scope is allowed to see other scopes when using the introspection endpoint + /// + public bool AllowUnrestrictedIntrospection { get; set; } + /// /// Creates a Scope with default values /// @@ -97,6 +102,7 @@ public Scope() IncludeAllClaimsForUser = false; Enabled = true; ShowInDiscoveryDocument = true; + AllowUnrestrictedIntrospection = false; } } } \ No newline at end of file diff --git a/source/Core/ResponseHandling/IntrospectionResponseGenerator.cs b/source/Core/ResponseHandling/IntrospectionResponseGenerator.cs new file mode 100644 index 000000000..db0e5a6c3 --- /dev/null +++ b/source/Core/ResponseHandling/IntrospectionResponseGenerator.cs @@ -0,0 +1,64 @@ +/* + * Copyright 2014, 2015 Dominick Baier, Brock Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using IdentityServer3.Core.Extensions; +using IdentityServer3.Core.Logging; +using IdentityServer3.Core.Models; +using IdentityServer3.Core.Validation; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace IdentityServer3.Core.ResponseHandling +{ + internal class IntrospectionResponseGenerator + { + private readonly static ILog Logger = LogProvider.GetCurrentClassLogger(); + + public Task> ProcessAsync(IntrospectionRequestValidationResult validationResult, Scope scope) + { + Logger.Info("Creating introspection response"); + + var response = new Dictionary(); + + if (validationResult.IsActive == false) + { + Logger.Info("Creating introspection response for inactive token."); + + response.Add("active", false); + return Task.FromResult(response); + } + + if (scope.AllowUnrestrictedIntrospection) + { + Logger.Info("Creating unrestricted introspection response for active token."); + + response = validationResult.Claims.ToClaimsDictionary(); + response.Add("active", true); + } + else + { + Logger.Info("Creating restricted introspection response for active token."); + + response = validationResult.Claims.Where(c => c.Type != Constants.ClaimTypes.Scope).ToClaimsDictionary(); + response.Add("active", true); + response.Add("scope", scope.Name); + } + + return Task.FromResult(response); + } + } +} \ No newline at end of file diff --git a/source/Core/Results/IntrospectionResult.cs b/source/Core/Results/IntrospectionResult.cs index 2be5b5492..c7d41f1be 100644 --- a/source/Core/Results/IntrospectionResult.cs +++ b/source/Core/Results/IntrospectionResult.cs @@ -30,23 +30,13 @@ namespace IdentityServer3.Core.Results { internal class IntrospectionResult : IHttpActionResult { - private readonly static ILog Logger = LogProvider.GetCurrentClassLogger(); private readonly static JsonMediaTypeFormatter Formatter = new JsonMediaTypeFormatter(); - public IntrospectionRequestValidationResult IntrospectionValidationResult { get; set; } - public Scope Scope { get; set; } + public Dictionary Result { get; private set; } - private readonly bool _sendInactive; - - public IntrospectionResult() - { - _sendInactive = true; - } - - public IntrospectionResult(IntrospectionRequestValidationResult introspectionValidationResult, Scope scope) + public IntrospectionResult(Dictionary result) { - IntrospectionValidationResult = introspectionValidationResult; - Scope = scope; + Result = result; } public Task ExecuteAsync(CancellationToken cancellationToken) @@ -56,29 +46,12 @@ public Task ExecuteAsync(CancellationToken cancellationToke private HttpResponseMessage Execute() { - if (_sendInactive == true) - { - var inactiveContent = new ObjectContent(new { active = false }, Formatter); - var inactiveMessage = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = inactiveContent - }; - - Logger.Info("Returning inactive introspection response."); - return inactiveMessage; - } - - var response = IntrospectionValidationResult.Claims.ToClaimsDictionary(); - response.Add("active", true); - response.Add("scope", Scope.Name); - - var content = new ObjectContent>(response, Formatter); + var content = new ObjectContent>(Result, Formatter); var message = new HttpResponseMessage(HttpStatusCode.OK) { Content = content }; - Logger.Info("Returning active introspection response."); return message; } } diff --git a/source/Core/Validation/IntrospectionRequestValidationResult.cs b/source/Core/Validation/IntrospectionRequestValidationResult.cs index 4bb2ae506..8876a9e2e 100644 --- a/source/Core/Validation/IntrospectionRequestValidationResult.cs +++ b/source/Core/Validation/IntrospectionRequestValidationResult.cs @@ -29,6 +29,7 @@ internal class IntrospectionRequestValidationResult : ValidationResult enum IntrospectionRequestValidationFailureReason { + None, MissingToken, InvalidToken, InvalidScope diff --git a/source/Core/Validation/IntrospectionRequestValidator.cs b/source/Core/Validation/IntrospectionRequestValidator.cs index f08c88485..51bb757b6 100644 --- a/source/Core/Validation/IntrospectionRequestValidator.cs +++ b/source/Core/Validation/IntrospectionRequestValidator.cs @@ -75,7 +75,7 @@ public async Task ValidateAsync(NameValueC IsActive = true, IsError = false, Token = token, - Claims = tokenValidationResult.Claims.Where(c => c.Type != Constants.ClaimTypes.Scope) + Claims = tokenValidationResult.Claims }; return success; diff --git a/source/Core/app.config b/source/Core/app.config index 9c179580a..450d0c525 100644 --- a/source/Core/app.config +++ b/source/Core/app.config @@ -1,43 +1,43 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + diff --git a/source/Core/packages.config b/source/Core/packages.config index f0183b902..dec8a3ffd 100644 --- a/source/Core/packages.config +++ b/source/Core/packages.config @@ -2,7 +2,7 @@ - + diff --git a/source/Host.Configuration/app.config b/source/Host.Configuration/app.config index fa4eaaf31..509c13a44 100644 --- a/source/Host.Configuration/app.config +++ b/source/Host.Configuration/app.config @@ -1,31 +1,31 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - + diff --git a/source/Host.Console/App.config b/source/Host.Console/App.config index f458b7c79..91659519b 100644 --- a/source/Host.Console/App.config +++ b/source/Host.Console/App.config @@ -1,33 +1,33 @@ - + - + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/source/Tests/UnitTests/Core.Tests.csproj b/source/Tests/UnitTests/Core.Tests.csproj index a207a979c..ba69e2019 100644 --- a/source/Tests/UnitTests/Core.Tests.csproj +++ b/source/Tests/UnitTests/Core.Tests.csproj @@ -61,7 +61,7 @@ True - ..\..\packages\IdentityModel.1.2.1\lib\net45\IdentityModel.Net45.dll + ..\..\packages\IdentityModel.1.3.0\lib\net45\IdentityModel.Net45.dll True diff --git a/source/Tests/UnitTests/Endpoints/Connect/Introspection/IntrospectionEndpointTests.cs b/source/Tests/UnitTests/Endpoints/Connect/Introspection/IntrospectionEndpointTests.cs index 69030ab59..7bffd9a6e 100644 --- a/source/Tests/UnitTests/Endpoints/Connect/Introspection/IntrospectionEndpointTests.cs +++ b/source/Tests/UnitTests/Endpoints/Connect/Introspection/IntrospectionEndpointTests.cs @@ -119,6 +119,46 @@ public async Task Valid_Token_Valid_Scope() response.IsActive.Should().Be(true); response.IsError.Should().Be(false); + + var scopes = from c in response.Claims + where c.Item1 == "scope" + select c; + + scopes.Count().Should().Be(1); + scopes.First().Item2.Should().Be("api1"); + } + + [Fact] + [Trait("Category", Category)] + public async Task Valid_Token_Valid_Unrestricted_Scope() + { + var tokenClient = new TokenClient( + TokenEndpoint, + "client1", + "secret", + _handler); + + var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1 api2 unrestricted.api"); + + var introspectionClient = new IntrospectionClient( + IntrospectionEndpoint, + "unrestricted.api", + "secret", + _handler); + + var response = await introspectionClient.SendAsync(new IntrospectionRequest + { + Token = tokenResponse.AccessToken + }); + + response.IsActive.Should().Be(true); + response.IsError.Should().Be(false); + + var scopes = from c in response.Claims + where c.Item1 == "scope" + select c; + + scopes.Count().Should().Be(3); } [Fact] @@ -146,6 +186,13 @@ public async Task Valid_Token_Valid_Scope_Multiple() response.IsActive.Should().Be(true); response.IsError.Should().Be(false); + + var scopes = from c in response.Claims + where c.Item1 == "scope" + select c; + + scopes.Count().Should().Be(1); + scopes.First().Item2.Should().Be("api1"); } [Fact] diff --git a/source/Tests/UnitTests/Endpoints/Connect/Introspection/Setup/Scopes.cs b/source/Tests/UnitTests/Endpoints/Connect/Introspection/Setup/Scopes.cs index 968e3d9a7..e9129672b 100644 --- a/source/Tests/UnitTests/Endpoints/Connect/Introspection/Setup/Scopes.cs +++ b/source/Tests/UnitTests/Endpoints/Connect/Introspection/Setup/Scopes.cs @@ -24,6 +24,16 @@ public static IEnumerable Get() new Scope { Name = "api2", + ScopeSecrets = new List + { + new Secret("secret".Sha256()) + } + }, + new Scope + { + Name = "unrestricted.api", + AllowUnrestrictedIntrospection = true, + ScopeSecrets = new List { new Secret("secret".Sha256()) @@ -32,4 +42,4 @@ public static IEnumerable Get() }; } } -} +} \ No newline at end of file diff --git a/source/Tests/UnitTests/app.config b/source/Tests/UnitTests/app.config index 974e6d00b..1755ae423 100644 --- a/source/Tests/UnitTests/app.config +++ b/source/Tests/UnitTests/app.config @@ -1,57 +1,57 @@ - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + diff --git a/source/Tests/UnitTests/packages.config b/source/Tests/UnitTests/packages.config index e1d0ddb91..5d189ea77 100644 --- a/source/Tests/UnitTests/packages.config +++ b/source/Tests/UnitTests/packages.config @@ -2,7 +2,7 @@ - +