Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add capability to encode kid header in request to Oauth2 server #4627

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
/**
* Provides OAuth2 client credentials flow support.
*/
@Provides({ IdentityService.class })
@Provides({IdentityService.class})
@Extension(value = Oauth2ServiceExtension.NAME)
public class Oauth2ServiceExtension implements ServiceExtension {

Expand Down Expand Up @@ -125,7 +125,12 @@ public void initialize(ServiceExtensionContext context) {

var certificate = Optional.ofNullable(certificateResolver.resolveCertificate(configuration.getPublicCertificateAlias()))
.orElseThrow(() -> new EdcException("Public certificate not found: " + configuration.getPublicCertificateAlias()));
jwtDecoratorRegistry.register(OAUTH2_TOKEN_CONTEXT, new Oauth2AssertionDecorator(configuration.getProviderAudience(), configuration.getClientId(), clock, configuration.getTokenExpiration()));
jwtDecoratorRegistry.register(OAUTH2_TOKEN_CONTEXT, Oauth2AssertionDecorator.Builder.newInstance()
.audience(configuration.getProviderAudience())
.clientId(configuration.getClientId())
.clock(clock)
.validity(configuration.getTokenExpiration())
.build());
jwtDecoratorRegistry.register(OAUTH2_TOKEN_CONTEXT, new X509CertificateDecorator(certificate));

providerKeyResolver = identityProviderKeyResolver(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.CLIENT_ID;
import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.CLIENT_SECRET_KEY;
import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.KEY_ID;
import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.PRIVATE_KEY_NAME;
import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.SCOPE;
import static org.eclipse.edc.iam.oauth2.spi.Oauth2DataAddressSchema.TOKEN_URL;
Expand Down Expand Up @@ -104,7 +105,13 @@ private Result<TokenRepresentation> createAssertion(String pkSecret, DataAddress
var validity = Optional.ofNullable(dataAddress.getStringProperty(VALIDITY))
.map(this::parseLong)
.orElse(DEFAULT_TOKEN_VALIDITY);
var decorator = new Oauth2AssertionDecorator(dataAddress.getStringProperty(TOKEN_URL), dataAddress.getStringProperty(CLIENT_ID), clock, validity);
var decorator = Oauth2AssertionDecorator.Builder.newInstance()
.audience(dataAddress.getStringProperty(TOKEN_URL))
.clientId(dataAddress.getStringProperty(CLIENT_ID))
.clock(clock)
.validity(validity)
.kid(dataAddress.getStringProperty(KEY_ID))
.build();
var service = new JwtGenerationService(jwsSignerProvider);

return service.generate(pkSecret, decorator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@

package org.eclipse.edc.iam.oauth2.spi;

import com.fasterxml.jackson.annotation.JsonCreator;
import org.eclipse.edc.spi.iam.TokenParameters;
import org.eclipse.edc.token.spi.KeyIdDecorator;
import org.eclipse.edc.token.spi.TokenDecorator;

import java.time.Clock;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
Expand All @@ -32,25 +35,72 @@

public class Oauth2AssertionDecorator implements TokenDecorator {

private final String audience;
private final String clientId;
private final Clock clock;
private final long validity;
private String audience;
private String clientId;
private Clock clock;
private long validity;
private String kid;

public Oauth2AssertionDecorator(String audience, String clientId, Clock clock, long validity) {
this.audience = audience;
this.clientId = clientId;
this.clock = clock;
this.validity = validity;
private Oauth2AssertionDecorator() {
}

@Override
public TokenParameters.Builder decorate(TokenParameters.Builder tokenParameters) {
new KeyIdDecorator(kid).decorate(tokenParameters);
return tokenParameters.claims(AUDIENCE, List.of(audience))
.claims(ISSUER, clientId)
.claims(SUBJECT, clientId)
.claims(JWT_ID, UUID.randomUUID().toString())
.claims(ISSUED_AT, Date.from(clock.instant()))
.claims(EXPIRATION_TIME, Date.from(clock.instant().plusSeconds(validity)));
}

public static class Builder {

private final Oauth2AssertionDecorator decorator;

private Builder() {
decorator = new Oauth2AssertionDecorator();
}

@JsonCreator
public static Oauth2AssertionDecorator.Builder newInstance() {
return new Oauth2AssertionDecorator.Builder();
}

public Builder audience(String audience) {
decorator.audience = audience;
return this;
}

public Builder clientId(String clientId) {
decorator.clientId = clientId;
return this;
}

public Builder clock(Clock clock) {
decorator.clock = clock;
return this;
}

public Builder validity(long validity) {
decorator.validity = validity;
return this;
}

public Builder kid(String kid) {
decorator.kid = kid;
return this;
}

public Oauth2AssertionDecorator build() {
Objects.requireNonNull(decorator.audience, "Audience must be set");
Objects.requireNonNull(decorator.clientId, "Client ID must be set");
if (decorator.validity <= 0) {
throw new IllegalArgumentException("Validity must be greater than 0");
}
decorator.clock = Objects.requireNonNullElse(decorator.clock, Clock.systemUTC());
return decorator;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public interface Oauth2DataAddressSchema {
String CLIENT_ID = "oauth2:clientId";
String CLIENT_SECRET_KEY = "oauth2:clientSecretKey";
String KEY_ID = "oauth2:kid";
String TOKEN_URL = "oauth2:tokenUrl";
String VALIDITY = "oauth2:validity";
String PRIVATE_KEY_NAME = "oauth2:privateKeyName";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
package org.eclipse.edc.iam.oauth2.spi;

import org.eclipse.edc.spi.iam.TokenParameters;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.time.Clock;
import java.time.Instant;
Expand All @@ -34,27 +36,32 @@
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT;

class Oauth2AssertionDecoratorTest {

private static final long TOKEN_EXPIRATION = 500;

private final Instant now = Instant.now();
private String audience;
private String clientId;
private Oauth2AssertionDecorator decorator;
private final Clock clock = Clock.fixed(now, UTC);
private final String audience = "test-audience";
private final String clientId = UUID.randomUUID().toString();

@BeforeEach
void setUp() {
audience = "test-audience";
clientId = UUID.randomUUID().toString();
var clock = Clock.fixed(now, UTC);
decorator = new Oauth2AssertionDecorator(audience, clientId, clock, TOKEN_EXPIRATION);
}
@ParameterizedTest
@EmptySource
@NullSource
@ValueSource(strings = {"test-kid", " ", " "})
void verifyDecorate(String kid) {
var decorator = Oauth2AssertionDecorator.Builder.newInstance()
.audience(audience)
.clientId(clientId)
.clock(clock)
.validity(TOKEN_EXPIRATION)
.kid(kid)
.build();

@Test
void verifyDecorate() {
var b = TokenParameters.Builder.newInstance();
decorator.decorate(b);

var t = b.build();
assertThat(t.getHeaders()).isEmpty();
assertThat(t.getHeaders().get("kid")).isEqualTo(kid);
assertThat(t.getClaims())
.hasEntrySatisfying(AUDIENCE, o -> assertThat(o).asInstanceOf(list(String.class)).contains(audience))
.hasFieldOrPropertyWithValue(ISSUER, clientId)
Expand All @@ -63,4 +70,5 @@ void verifyDecorate() {
.hasEntrySatisfying(ISSUED_AT, issueDate -> assertThat((Date) issueDate).isEqualTo(now))
.hasEntrySatisfying(EXPIRATION_TIME, expiration -> assertThat((Date) expiration).isEqualTo(now.plusSeconds(TOKEN_EXPIRATION)));
}

}
Loading