Browse Source

Remove authorization code/identity token introspection/revocation support

pull/1054/head 3.0.0-beta3
Kévin Chalet 6 years ago
parent
commit
22f5632afe
  1. 16
      src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx
  2. 45
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  3. 40
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  4. 205
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  5. 59
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

16
src/OpenIddict.Abstractions/Resources/OpenIddictResources.resx

@ -2184,18 +2184,10 @@ The principal used to create the token contained the following claims: {Claims}.
<value>The introspection request was rejected because the received token was of an unsupported type.</value> <value>The introspection request was rejected because the received token was of an unsupported type.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>
</data> </data>
<data name="ID7105" xml:space="preserve">
<value>The introspection request was rejected because the authorization code was issued to a different client.</value>
<comment>{Locked}</comment>
</data>
<data name="ID7106" xml:space="preserve"> <data name="ID7106" xml:space="preserve">
<value>The introspection request was rejected because the access token was issued to a different client or for another resource server.</value> <value>The introspection request was rejected because the access token was issued to a different client or for another resource server.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>
</data> </data>
<data name="ID7107" xml:space="preserve">
<value>The introspection request was rejected because the identity token was issued to a different client.</value>
<comment>{Locked}</comment>
</data>
<data name="ID7108" xml:space="preserve"> <data name="ID7108" xml:space="preserve">
<value>The introspection request was rejected because the refresh token was issued to a different client.</value> <value>The introspection request was rejected because the refresh token was issued to a different client.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>
@ -2236,18 +2228,10 @@ The principal used to create the token contained the following claims: {Claims}.
<value>The revocation request was rejected because the received token was of an unsupported type.</value> <value>The revocation request was rejected because the received token was of an unsupported type.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>
</data> </data>
<data name="ID7118" xml:space="preserve">
<value>The revocation request was rejected because the authorization code was issued to a different client.</value>
<comment>{Locked}</comment>
</data>
<data name="ID7119" xml:space="preserve"> <data name="ID7119" xml:space="preserve">
<value>The revocation request was rejected because the access token was issued to a different client or for another resource server.</value> <value>The revocation request was rejected because the access token was issued to a different client or for another resource server.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>
</data> </data>
<data name="ID7120" xml:space="preserve">
<value>The revocation request was rejected because the identity token was issued to a different client.</value>
<comment>{Locked}</comment>
</data>
<data name="ID7121" xml:space="preserve"> <data name="ID7121" xml:space="preserve">
<value>The revocation request was rejected because the refresh token was issued to a different client.</value> <value>The revocation request was rejected because the refresh token was issued to a different client.</value>
<comment>{Locked}</comment> <comment>{Locked}</comment>

45
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

@ -741,8 +741,6 @@ namespace OpenIddict.Server
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006)); Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
!context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode) &&
!context.Principal.HasTokenType(TokenTypeHints.IdToken) &&
!context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) !context.Principal.HasTokenType(TokenTypeHints.RefreshToken))
{ {
context.Logger.LogError(SR.GetResourceString(SR.ID7104)); context.Logger.LogError(SR.GetResourceString(SR.ID7104));
@ -789,29 +787,6 @@ namespace OpenIddict.Server
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID5000(Parameters.ClientId)); Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID5000(Parameters.ClientId));
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006)); Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
// When the introspected token is an authorization code, the caller must be
// listed as a presenter (i.e the party the authorization code was issued to).
if (context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode))
{
if (!context.Principal.HasPresenter())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1042));
}
if (!context.Principal.HasPresenter(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7105));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3077]);
return default;
}
return default;
}
// When the introspected token is an access token, the caller must be listed either as a presenter // When the introspected token is an access token, the caller must be listed either as a presenter
// (i.e the party the token was issued to) or as an audience (i.e a resource server/API). // (i.e the party the token was issued to) or as an audience (i.e a resource server/API).
// If the access token doesn't contain any explicit presenter/audience, the token is assumed // If the access token doesn't contain any explicit presenter/audience, the token is assumed
@ -829,22 +804,6 @@ namespace OpenIddict.Server
return default; return default;
} }
// When the introspected token is an identity token, the caller must be listed as an audience
// (i.e the client application the identity token was initially issued to).
// If the identity token doesn't contain any explicit audience, the token is
// assumed to be not specific to any client application and the check is bypassed.
if (context.Principal.HasTokenType(TokenTypeHints.IdToken) &&
context.Principal.HasAudience() && !context.Principal.HasAudience(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7107));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3077]);
return default;
}
// When the introspected token is a refresh token, the caller must be // When the introspected token is a refresh token, the caller must be
// listed as a presenter (i.e the party the token was issued to). // listed as a presenter (i.e the party the token was issued to).
// If the refresh token doesn't contain any explicit presenter, the token is // If the refresh token doesn't contain any explicit presenter, the token is
@ -985,8 +944,8 @@ namespace OpenIddict.Server
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), SR.FormatID5000(Parameters.ClientId)); Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), SR.FormatID5000(Parameters.ClientId));
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006)); Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
// Don't return application-specific claims if the token is not an access or identity token. // Don't return application-specific claims if the token is not an access token.
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && !context.Principal.HasTokenType(TokenTypeHints.IdToken)) if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken))
{ {
return; return;
} }

40
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -687,7 +687,6 @@ namespace OpenIddict.Server
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006)); Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
!context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode) &&
!context.Principal.HasTokenType(TokenTypeHints.RefreshToken)) !context.Principal.HasTokenType(TokenTypeHints.RefreshToken))
{ {
context.Logger.LogError(SR.GetResourceString(SR.ID7117)); context.Logger.LogError(SR.GetResourceString(SR.ID7117));
@ -734,29 +733,6 @@ namespace OpenIddict.Server
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID5000(Parameters.ClientId)); Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID5000(Parameters.ClientId));
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006)); Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
// When the revoked token is an authorization code, the caller must be
// listed as a presenter (i.e the party the authorization code was issued to).
if (context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode))
{
if (!context.Principal.HasPresenter())
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID1042));
}
if (!context.Principal.HasPresenter(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7118));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
return default;
}
// When the revoked token is an access token, the caller must be listed either as a presenter // When the revoked token is an access token, the caller must be listed either as a presenter
// (i.e the party the token was issued to) or as an audience (i.e a resource server/API). // (i.e the party the token was issued to) or as an audience (i.e a resource server/API).
// If the access token doesn't contain any explicit presenter/audience, the token is assumed // If the access token doesn't contain any explicit presenter/audience, the token is assumed
@ -774,22 +750,6 @@ namespace OpenIddict.Server
return default; return default;
} }
// When the revoked token is an identity token, the caller must be listed as an audience
// (i.e the client application the identity token was initially issued to).
// If the identity token doesn't contain any explicit audience, the token is
// assumed to be not specific to any client application and the check is bypassed.
if (context.Principal.HasTokenType(TokenTypeHints.IdToken) &&
context.Principal.HasAudience() && !context.Principal.HasAudience(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID7120));
context.Reject(
error: Errors.InvalidToken,
description: context.Localizer[SR.ID3080]);
return default;
}
// When the revoked token is a refresh token, the caller must be // When the revoked token is a refresh token, the caller must be
// listed as a presenter (i.e the party the token was issued to). // listed as a presenter (i.e the party the token was issued to).
// If the refresh token doesn't contain any explicit presenter, the token is // If the refresh token doesn't contain any explicit presenter, the token is

205
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs

@ -5,7 +5,6 @@
*/ */
using System; using System;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json; using System.Text.Json;
@ -221,49 +220,13 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal(SR.GetResourceString(SR.ID3019), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID3019), response.ErrorDescription);
} }
[Fact] [Theory]
public async Task ValidateIntrospectionRequest_AuthorizationCodeCausesAnErrorWhenPresentersAreMissing() [InlineData(TokenTypeHints.AuthorizationCode)]
{ [InlineData(TokenTypeHints.DeviceCode)]
// Arrange [InlineData(TokenTypeHints.IdToken)]
await using var server = await CreateServerAsync(options => [InlineData(TokenTypeHints.UserCode)]
{ [InlineData("custom_token")]
options.EnableDegradedMode(); public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type)
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SlAV32hkKG", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters(Enumerable.Empty<string>());
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
});
await using var client = await server.CreateClientAsync();
// Act and assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate
{
return client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "SlAV32hkKG",
TokenTypeHint = TokenTypeHints.AuthorizationCode
});
});
Assert.Equal(SR.GetResourceString(SR.ID1042), exception.Message);
}
[Fact]
public async Task ValidateIntrospectionRequest_AuthorizationCodeCausesAnErrorWhenCallerIsNotAValidPresenter()
{ {
// Arrange // Arrange
await using var server = await CreateServerAsync(options => await using var server = await CreateServerAsync(options =>
@ -274,11 +237,10 @@ namespace OpenIddict.Server.IntegrationTests
{ {
builder.UseInlineHandler(context => builder.UseInlineHandler(context =>
{ {
Assert.Equal("SlAV32hkKG", context.Token); Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode) .SetTokenType(type);
.SetPresenters("Contoso");
return default; return default;
}); });
@ -295,13 +257,12 @@ namespace OpenIddict.Server.IntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{ {
ClientId = "Fabrikam", ClientId = "Fabrikam",
Token = "SlAV32hkKG", Token = "5HtRgAtc02"
TokenTypeHint = TokenTypeHints.AuthorizationCode
}); });
// Assert // Assert
Assert.Equal(Errors.InvalidToken, response.Error); Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID3077), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID3076), response.ErrorDescription);
} }
[Fact] [Fact]
@ -347,48 +308,6 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal(SR.GetResourceString(SR.ID3077), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID3077), response.ErrorDescription);
} }
[Fact]
public async Task ValidateIntrospectionRequest_IdentityTokenCausesAnErrorWhenCallerIsNotAValidAudience()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(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);
});
await using var client = await server.CreateClientAsync();
// 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(SR.GetResourceString(SR.ID3077), response.ErrorDescription);
}
[Fact] [Fact]
public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter() public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{ {
@ -847,49 +766,6 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal("Contoso", (string) response[Claims.ClientId]); Assert.Equal("Contoso", (string) response[Claims.ClientId]);
} }
[Fact]
public async Task HandleIntrospectionRequest_NonBasicAuthorizationCodeClaimsAreNotReturned()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(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);
});
});
await using var client = await server.CreateClientAsync();
// 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] [Fact]
public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned() public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned()
{ {
@ -993,63 +869,6 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal("openid profile", (string) response[Claims.Scope]); 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<CancellationToken>()))
.ReturnsAsync(application);
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Confidential, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(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);
});
await using var client = await server.CreateClientAsync();
// 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] [Fact]
public async Task HandleIntrospectionRequest_ClaimValueTypesAreHonored() public async Task HandleIntrospectionRequest_ClaimValueTypesAreHonored()
{ {

59
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

@ -153,8 +153,13 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal(SR.FormatID3029(Parameters.Token), response.ErrorDescription); Assert.Equal(SR.FormatID3029(Parameters.Token), response.ErrorDescription);
} }
[Fact] [Theory]
public async Task ValidateRevocationRequest_IdentityTokenCausesAnUnsupportedTokenTypeError() [InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type)
{ {
// Arrange // Arrange
await using var server = await CreateServerAsync(options => await using var server = await CreateServerAsync(options =>
@ -165,11 +170,10 @@ namespace OpenIddict.Server.IntegrationTests
{ {
builder.UseInlineHandler(context => builder.UseInlineHandler(context =>
{ {
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token); Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.IdToken) .SetTokenType(type);
.SetAudiences("AdventureWorks");
return default; return default;
}); });
@ -186,8 +190,7 @@ namespace OpenIddict.Server.IntegrationTests
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{ {
ClientId = "Fabrikam", ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA", Token = "5HtRgAtc02"
TokenTypeHint = TokenTypeHints.IdToken
}); });
// Assert // Assert
@ -195,48 +198,6 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal(SR.GetResourceString(SR.ID3079), response.ErrorDescription); Assert.Equal(SR.GetResourceString(SR.ID3079), response.ErrorDescription);
} }
[Fact]
public async Task ValidateRevocationRequest_AuthorizationCodeCausesAnErrorWhenCallerIsNotAValidPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(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);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "SlAV32hkKG",
TokenTypeHint = TokenTypeHints.AuthorizationCode
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID3080), response.ErrorDescription);
}
[Fact] [Fact]
public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter() public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
{ {

Loading…
Cancel
Save