Skip to content
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
@@ -1,13 +1,20 @@
package org.opendevstack.apiservice.core.security.flow.validator;

import org.opendevstack.apiservice.core.contracts.auth.AuthType;
import org.opendevstack.apiservice.core.security.client.credentials.ClientCredentialsTokenProperties;
import org.opendevstack.apiservice.core.security.flow.AuthFlowValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;

@Component
public class ClientCredentialFlowValidator implements AuthFlowValidator {

private final ClientCredentialsTokenProperties clientCredentialsTokenProperties;

public ClientCredentialFlowValidator(ClientCredentialsTokenProperties clientCredentialsTokenProperties) {
this.clientCredentialsTokenProperties = clientCredentialsTokenProperties;
}

@Override
public AuthType getSupportedFlow() {
return AuthType.CLIENT_CREDENTIALS;
Expand All @@ -27,6 +34,12 @@ public boolean validate(Jwt jwt) {
return false;
}

// The audience must contain the configured client-id (app.security.client-credentials.client-id)
String configuredClientId = clientCredentialsTokenProperties.getClientId();
if (configuredClientId == null || configuredClientId.isBlank() || !aud.toString().contains(configuredClientId)) {
return false;
}

String scp = jwt.getClaimAsString("scp");
if (scp != null && !scp.isBlank()) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opendevstack.apiservice.core.security.client.credentials.ClientCredentialsTokenProperties;
import org.springframework.security.oauth2.jwt.Jwt;

import java.time.Instant;
Expand All @@ -12,11 +13,15 @@

class ClientCredentialFlowValidatorTest {

private static final String CONFIGURED_CLIENT_ID = "my-configured-client-id";

private ClientCredentialFlowValidator validator;

@BeforeEach
void setUp() {
validator = new ClientCredentialFlowValidator();
ClientCredentialsTokenProperties properties = new ClientCredentialsTokenProperties();
properties.setClientId(CONFIGURED_CLIENT_ID);
validator = new ClientCredentialFlowValidator(properties);
}

// --- v1 token (appid) ---
Expand All @@ -25,7 +30,7 @@ void setUp() {
void validate_v1Token_valid() {
Jwt jwt = buildJwt(Map.of(
"appid", "client-id-v1",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"oid", "same-as-sub"
), "same-as-sub");
assertTrue(validator.validate(jwt));
Expand All @@ -35,7 +40,7 @@ void validate_v1Token_valid() {
void validate_v1Token_withScp_invalid() {
Jwt jwt = buildJwt(Map.of(
"appid", "client-id-v1",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"scp", "api.read",
"oid", "same-as-sub"
), "same-as-sub");
Expand All @@ -46,7 +51,7 @@ void validate_v1Token_withScp_invalid() {
void validate_v1Token_withUpn_invalid() {
Jwt jwt = buildJwt(Map.of(
"appid", "client-id-v1",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"upn", "user@example.com",
"oid", "same-as-sub"
), "same-as-sub");
Expand All @@ -57,7 +62,7 @@ void validate_v1Token_withUpn_invalid() {
void validate_v1Token_subNotEqualOid_invalid() {
Jwt jwt = buildJwt(Map.of(
"appid", "client-id-v1",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"oid", "different-oid"
), "subject");
assertFalse(validator.validate(jwt));
Expand All @@ -69,7 +74,7 @@ void validate_v1Token_subNotEqualOid_invalid() {
void validate_v2Token_valid() {
Jwt jwt = buildJwt(Map.of(
"azp", "client-id-v2",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"oid", "same-as-sub"
), "same-as-sub");
assertTrue(validator.validate(jwt));
Expand All @@ -79,7 +84,7 @@ void validate_v2Token_valid() {
void validate_v2Token_withScp_invalid() {
Jwt jwt = buildJwt(Map.of(
"azp", "client-id-v2",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"scp", "api.read",
"oid", "same-as-sub"
), "same-as-sub");
Expand All @@ -90,7 +95,7 @@ void validate_v2Token_withScp_invalid() {
void validate_v2Token_withPreferredUsername_invalid() {
Jwt jwt = buildJwt(Map.of(
"azp", "client-id-v2",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"preferred_username", "user@example.com",
"oid", "same-as-sub"
), "same-as-sub");
Expand All @@ -101,7 +106,7 @@ void validate_v2Token_withPreferredUsername_invalid() {
void validate_v2Token_subNotEqualOid_invalid() {
Jwt jwt = buildJwt(Map.of(
"azp", "client-id-v2",
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"oid", "different-oid"
), "subject");
assertFalse(validator.validate(jwt));
Expand All @@ -112,7 +117,7 @@ void validate_v2Token_subNotEqualOid_invalid() {
@Test
void validate_noClientIdClaim_invalid() {
Jwt jwt = buildJwt(Map.of(
"aud", "api://my-app",
"aud", "api://" + CONFIGURED_CLIENT_ID,
"oid", "same-as-sub"
), "same-as-sub");
assertFalse(validator.validate(jwt));
Expand All @@ -127,6 +132,18 @@ void validate_missingAud_invalid() {
assertFalse(validator.validate(jwt));
}

// --- audience validation ---

@Test
void validate_audDoesNotContainConfiguredClientId_invalid() {
Jwt jwt = buildJwt(Map.of(
"appid", "client-id-v1",
"aud", "some-other-client-id",
"oid", "same-as-sub"
), "same-as-sub");
assertFalse(validator.validate(jwt));
}

private Jwt buildJwt(Map<String, Object> extraClaims, String subject) {
return Jwt.withTokenValue("token")
.header("alg", "RS256")
Expand Down
Loading