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>
<comment>{Locked}</comment>
</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">
<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>
</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">
<value>The introspection request was rejected because the refresh token was issued to a different client.</value>
<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>
<comment>{Locked}</comment>
</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">
<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>
</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">
<value>The revocation request was rejected because the refresh token was issued to a different client.</value>
<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));
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
!context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode) &&
!context.Principal.HasTokenType(TokenTypeHints.IdToken) &&
!context.Principal.HasTokenType(TokenTypeHints.RefreshToken))
{
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(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
// (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
@ -829,22 +804,6 @@ namespace OpenIddict.Server
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
// 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
@ -985,8 +944,8 @@ namespace OpenIddict.Server
Debug.Assert(!string.IsNullOrEmpty(context.Request.ClientId), SR.FormatID5000(Parameters.ClientId));
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.
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) && !context.Principal.HasTokenType(TokenTypeHints.IdToken))
// Don't return application-specific claims if the token is not an access token.
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken))
{
return;
}

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

@ -687,7 +687,6 @@ namespace OpenIddict.Server
Debug.Assert(context.Principal != null, SR.GetResourceString(SR.ID5006));
if (!context.Principal.HasTokenType(TokenTypeHints.AccessToken) &&
!context.Principal.HasTokenType(TokenTypeHints.AuthorizationCode) &&
!context.Principal.HasTokenType(TokenTypeHints.RefreshToken))
{
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(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
// (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
@ -774,22 +750,6 @@ namespace OpenIddict.Server
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
// 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

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

@ -5,7 +5,6 @@
*/
using System;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Text.Json;
@ -221,49 +220,13 @@ namespace OpenIddict.Server.IntegrationTests
Assert.Equal(SR.GetResourceString(SR.ID3019), response.ErrorDescription);
}
[Fact]
public async Task ValidateIntrospectionRequest_AuthorizationCodeCausesAnErrorWhenPresentersAreMissing()
{
// 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(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()
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
@ -274,11 +237,10 @@ namespace OpenIddict.Server.IntegrationTests
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SlAV32hkKG", context.Token);
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Contoso");
.SetTokenType(type);
return default;
});
@ -295,13 +257,12 @@ namespace OpenIddict.Server.IntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "SlAV32hkKG",
TokenTypeHint = TokenTypeHints.AuthorizationCode
Token = "5HtRgAtc02"
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID3077), response.ErrorDescription);
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID3076), response.ErrorDescription);
}
[Fact]
@ -347,48 +308,6 @@ namespace OpenIddict.Server.IntegrationTests
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]
public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{
@ -847,49 +766,6 @@ namespace OpenIddict.Server.IntegrationTests
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]
public async Task HandleIntrospectionRequest_NonBasicRefreshTokenClaimsAreNotReturned()
{
@ -993,63 +869,6 @@ namespace OpenIddict.Server.IntegrationTests
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]
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);
}
[Fact]
public async Task ValidateRevocationRequest_IdentityTokenCausesAnUnsupportedTokenTypeError()
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
@ -165,11 +170,10 @@ namespace OpenIddict.Server.IntegrationTests
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.IdToken)
.SetAudiences("AdventureWorks");
.SetTokenType(type);
return default;
});
@ -186,8 +190,7 @@ namespace OpenIddict.Server.IntegrationTests
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.IdToken
Token = "5HtRgAtc02"
});
// Assert
@ -195,48 +198,6 @@ namespace OpenIddict.Server.IntegrationTests
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]
public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
{

Loading…
Cancel
Save