Skip to content

Commit

Permalink
feat: add resource parameter to the OAuth2 token request to follow RF…
Browse files Browse the repository at this point in the history
…C-8707
  • Loading branch information
scandinave committed Jan 7, 2025
1 parent 197d791 commit 4edce0d
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 17 deletions.
23 changes: 12 additions & 11 deletions extensions/common/iam/oauth2/oauth2-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ This extension provides an `IdentityService` implementation based on the OAuth2

## Configuration

| Parameter name | Description | Mandatory | Default value |
|:----------------------------------|:-------------------------------------------------------------------------------------------|:----------|:------------------------------------|
| `edc.oauth.token.url` | URL of the authorization server | true | null |
| `edc.oauth.provider.audience` | Provider audience to be put in the outgoing token as 'aud' claim | false | id of the connector |
| `edc.oauth.endpoint.audience` | Endpoint audience to verify incoming token 'aud' claim | false | `edc.oauth.provider.audience` value |
| `edc.oauth.provider.jwks.url` | URL from which well-known public keys of Authorization server can be fetched | false | http://localhost/empty_jwks_url |
| `edc.oauth.certificate.alias` | Alias of public associated with client certificate | true | null |
| `edc.oauth.private.key.alias` | Alias of private key (used to sign the token) | true | null |
| `edc.oauth.provider.jwks.refresh` | Interval at which public keys are refreshed from Authorization server (in minutes) | false | 5 |
| `edc.oauth.client.id` | Public identifier of the client | true | null |
| `edc.oauth.validation.nbf.leeway` | Leeway in seconds added to current time to remedy clock skew on notBefore claim validation | false | 10 |
| Parameter name | Description | Mandatory | Default value |
|:-----------------------------------|:---------------------------------------------------------------------------------------------------------------------|:----------|:------------------------------------|
| `edc.oauth.token.url` | URL of the authorization server | true | null |
| `edc.oauth.provider.audience` | Provider audience to be put in the outgoing token as 'aud' claim | false | id of the connector |
| `edc.oauth.endpoint.audience` | Endpoint audience to verify incoming token 'aud' claim | false | `edc.oauth.provider.audience` value |
| `edc.oauth.provider.jwks.url` | URL from which well-known public keys of Authorization server can be fetched | false | http://localhost/empty_jwks_url |
| `edc.oauth.certificate.alias` | Alias of public associated with client certificate | true | null |
| `edc.oauth.private.key.alias` | Alias of private key (used to sign the token) | true | null |
| `edc.oauth.provider.jwks.refresh` | Interval at which public keys are refreshed from Authorization server (in minutes) | false | 5 |
| `edc.oauth.client.id` | Public identifier of the client | true | null |
| `edc.oauth.validation.nbf.leeway` | Leeway in seconds added to current time to remedy clock skew on notBefore claim validation | false | 10 |
| `edc.oauth.token.resource.enabled` | Adds `resource` form parameter in the access token request. Allows to specify an audience as defined in the RFC-8707 | false | false |

## Extensions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class Oauth2ServiceConfiguration {
@Setting(description = "Token expiration in minutes. By default is 5 minutes", key = "edc.oauth.token.expiration", defaultValue = DEFAULT_TOKEN_EXPIRATION + "")
private Long tokenExpiration;

@Setting(description = "Enable the connector to request a token with a specific audience as defined in the RFC-8707.", key = "edc.oauth.token.resource.enabled", defaultValue = "false")
private boolean tokenResourceEnabled;

private Oauth2ServiceConfiguration() {

}
Expand Down Expand Up @@ -92,6 +95,10 @@ public Long getTokenExpiration() {
return tokenExpiration;
}

public boolean isTokenResourceEnabled() {
return tokenResourceEnabled;
}

public int getProviderJwksRefresh() {
return providerJwksRefresh;
}
Expand Down Expand Up @@ -161,6 +168,11 @@ public Builder tokenExpiration(long tokenExpiration) {
return this;
}

public Builder tokenResourceEnabled(boolean tokenResourceEnabled) {
configuration.tokenResourceEnabled = tokenResourceEnabled;
return this;
}

public Oauth2ServiceConfiguration build() {
return configuration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ private Oauth2ServiceImpl createOauth2Service(Oauth2ServiceConfiguration configu
jwtDecoratorRegistry,
tokenValidationRulesRegistry,
tokenValidationService,
providerKeyResolver
providerKeyResolver,
configuration.isTokenResourceEnabled()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.edc.iam.oauth2.spi.client.Oauth2Client;
import org.eclipse.edc.iam.oauth2.spi.client.Oauth2CredentialsRequest;
import org.eclipse.edc.iam.oauth2.spi.client.PrivateKeyOauth2CredentialsRequest;
import org.eclipse.edc.iam.oauth2.spi.client.PrivateKeyOauth2CredentialsRequest.Builder;
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
import org.eclipse.edc.keys.spi.PublicKeyResolver;
import org.eclipse.edc.spi.iam.ClaimToken;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class Oauth2ServiceImpl implements IdentityService {
private final TokenValidationService tokenValidationService;
private final PublicKeyResolver publicKeyResolver;
private final TokenValidationRulesRegistry tokenValidationRuleRegistry;
private final boolean tokenResourceEnabled;

/**
* Creates a new instance of the OAuth2 Service
Expand All @@ -63,10 +65,11 @@ public class Oauth2ServiceImpl implements IdentityService {
* @param client client for Oauth2 server
* @param jwtDecoratorRegistry Registry containing the decorator for build the JWT
* @param tokenValidationService Service used for token validation
* @param tokenResourceEnabled Add support for generating access token request with resource parameter
*/
public Oauth2ServiceImpl(String tokenUrl, TokenGenerationService tokenGenerationService, Supplier<String> privateKeyIdSupplier,
Oauth2Client client, TokenDecoratorRegistry jwtDecoratorRegistry, TokenValidationRulesRegistry tokenValidationRuleRegistry, TokenValidationService tokenValidationService,
PublicKeyResolver publicKeyResolver) {
PublicKeyResolver publicKeyResolver, boolean tokenResourceEnabled) {
this.tokenUrl = tokenUrl;
this.privateKeySupplier = privateKeyIdSupplier;
this.client = client;
Expand All @@ -75,6 +78,7 @@ public Oauth2ServiceImpl(String tokenUrl, TokenGenerationService tokenGeneration
this.tokenGenerationService = tokenGenerationService;
this.tokenValidationService = tokenValidationService;
this.publicKeyResolver = publicKeyResolver;
this.tokenResourceEnabled = tokenResourceEnabled;
}

@Override
Expand All @@ -98,12 +102,16 @@ private Result<String> generateClientAssertion() {

@NotNull
private Oauth2CredentialsRequest createRequest(TokenParameters parameters, String assertion) {
return PrivateKeyOauth2CredentialsRequest.Builder.newInstance()
PrivateKeyOauth2CredentialsRequest.Builder<?> builder = Builder.newInstance()
.url(tokenUrl)
.clientAssertion(assertion)
.scope(parameters.getStringClaim(JwtRegisteredClaimNames.SCOPE))
.grantType(GRANT_TYPE)
.build();
.grantType(GRANT_TYPE);

if (tokenResourceEnabled) {
builder.resource(parameters.getStringClaim(JwtRegisteredClaimNames.AUDIENCE));
}
return builder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ void setUp() throws JOSEException {
.publicCertificateAlias(PUBLIC_CERTIFICATE_ALIAS)
.providerAudience(PROVIDER_AUDIENCE)
.endpointAudience(ENDPOINT_AUDIENCE)
.tokenResourceEnabled(true)
.build();

var tokenValidationService = new TokenValidationServiceImpl();
Expand All @@ -114,7 +115,7 @@ void setUp() throws JOSEException {
registry.addRule(OAUTH2_TOKEN_CONTEXT, new ExpirationIssuedAtValidationRule(Clock.systemUTC(), configuration.getIssuedAtLeeway()));

authService = new Oauth2ServiceImpl(configuration.getTokenUrl(), tokenGenerationService, () -> TEST_PRIVATE_KEY_ID, client, jwtDecoratorRegistry, registry,
tokenValidationService, publicKeyResolverMock);
tokenValidationService, publicKeyResolverMock, configuration.isTokenResourceEnabled());

}

Expand Down Expand Up @@ -143,6 +144,7 @@ void obtainClientCredentials() {
assertThat(capturedRequest.getScope()).isEqualTo("scope");
assertThat(capturedRequest.getClientAssertion()).isEqualTo("assertionToken");
assertThat(capturedRequest.getClientAssertionType()).isEqualTo("urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
assertThat(capturedRequest.getResource()).isEqualTo("audience");
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public abstract class Oauth2CredentialsRequest {

private static final String GRANT_TYPE = "grant_type";
private static final String SCOPE = "scope";
private static final String RESOURCE = "resource";

protected String url;
protected final Map<String, String> params = new HashMap<>();
Expand All @@ -44,6 +45,16 @@ public String getGrantType() {
return params.get(GRANT_TYPE);
}

/**
* The audience for which an access token will be requested.
*
* @return The value of the resource form parameter.
*/
@Nullable
public String getResource() {
return this.params.get(RESOURCE);
}

public Map<String, String> getParams() {
return params;
}
Expand Down Expand Up @@ -80,12 +91,24 @@ public B params(Map<String, String> params) {
return self();
}

/**
* Adds the resource form parameter to the request.
*
* @param targetedAudience The audience for which an access token will be requested.
* @see <a href="https://www.rfc-editor.org/rfc/rfc8707.html">RFC-8707</a>
* @return this builder
*/
public B resource(String targetedAudience) {
return param(RESOURCE, targetedAudience);
}

public abstract B self();

protected T build() {
Objects.requireNonNull(request.url, "url");
Objects.requireNonNull(request.params.get(GRANT_TYPE), GRANT_TYPE);
return request;
}

}
}

0 comments on commit 4edce0d

Please sign in to comment.