Skip to content

Commit

Permalink
[ELY-2752] Ensure it's possible to make use of a custom principal-att…
Browse files Browse the repository at this point in the history
…ribute value for OIDC
  • Loading branch information
fjuma committed May 13, 2024
1 parent 4f2b26d commit ac72762
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,9 @@ interface ElytronMessages extends BasicLogger {
@Message(id = 23056, value = "No message entity")
IOException noMessageEntity();

@LogMessage(level = DEBUG)
@Message(id = 23057, value = "principal-attribute '%s' claim does not exist, falling back to 'sub'")
void principalAttributeClaimDoesNotExist(String principalAttributeClaim);

}

Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,14 @@ public String getPrincipalName(OidcClientConfiguration deployment) {
case NICKNAME:
return getNickName();
default:
return getSubject();
String claimValue = getClaimValueAsString(attr);
if (claimValue != null) {
return claimValue;
} else {
// fall back to sub claim
log.principalAttributeClaimDoesNotExist(attr);
return getSubject();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public class OidcBaseTest extends AbstractBaseHttpTest {
public static final String CLIENT_SECRET = "secret";
public static KeycloakContainer KEYCLOAK_CONTAINER;
public static final String TEST_REALM = "WildFly";
public static final String TEST_REALM_WITH_SCOPES = "WildFlyScopes";
public static final String TENANT1_REALM = "tenant1";
public static final String TENANT2_REALM = "tenant2";
public static final String KEYCLOAK_USERNAME = "username";
Expand Down Expand Up @@ -131,15 +132,26 @@ protected static boolean isDockerAvailable() {
}
}
protected CallbackHandler getCallbackHandler() {
return getCallbackHandler(false, null);
return getCallbackHandler(false, null, null);
}

protected CallbackHandler getCallbackHandler(String expectedPrincipal) {
return getCallbackHandler(false, null, expectedPrincipal);
}

protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes) {
return getCallbackHandler(checkScope, expectedScopes, null);
}

protected CallbackHandler getCallbackHandler(boolean checkScope, String expectedScopes, String expectedPrincipal) {
return callbacks -> {
for(Callback callback : callbacks) {
if (callback instanceof EvidenceVerifyCallback) {
Evidence evidence = ((EvidenceVerifyCallback) callback).getEvidence();
((EvidenceVerifyCallback) callback).setVerified(evidence.getDecodedPrincipal() != null);
if (expectedPrincipal != null) {
assertEquals(expectedPrincipal, evidence.getDecodedPrincipal().getName());
}
} else if (callback instanceof AuthenticationCompleteCallback) {
// NO-OP
} else if (callback instanceof IdentityCredentialCallback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.callback.CallbackHandler;

import org.apache.http.HttpStatus;
import org.junit.AfterClass;
import org.junit.BeforeClass;
Expand Down Expand Up @@ -70,7 +72,8 @@ public static void startTestContainers() throws Exception {
assumeTrue("Docker isn't available, OIDC tests will be skipped", isDockerAvailable());
KEYCLOAK_CONTAINER = new KeycloakContainer();
KEYCLOAK_CONTAINER.start();
sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, CONFIGURE_CLIENT_SCOPES));
sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, false));
sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TEST_REALM_WITH_SCOPES, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, CONFIGURE_CLIENT_SCOPES));
sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT1_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true));
sendRealmCreationRequest(KeycloakConfiguration.getRealmRepresentation(TENANT2_REALM, CLIENT_ID, CLIENT_SECRET, CLIENT_HOST_NAME, CLIENT_PORT, CLIENT_APP, ACCESS_TOKEN_LIFESPAN, SESSION_MAX_LIFESPAN, false, true));
client = new MockWebServer();
Expand All @@ -85,6 +88,11 @@ public static void generalCleanup() throws Exception {
.auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
.when()
.delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM).then().statusCode(204);
RestAssured
.given()
.auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
.when()
.delete(KEYCLOAK_CONTAINER.getAuthServerUrl() + "/admin/realms/" + TEST_REALM_WITH_SCOPES).then().statusCode(204);
RestAssured
.given()
.auth().oauth2(KeycloakConfiguration.getAdminAccessToken(KEYCLOAK_CONTAINER.getAuthServerUrl()))
Expand Down Expand Up @@ -235,6 +243,31 @@ private void performAuthentication(InputStream oidcConfig, String username, Stri
performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation, clientPageText, null, false);
}

private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
CallbackHandler callbackHandler) throws Exception {
performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation,
clientPageText, null, false, callbackHandler);
}

@Test
public void testPrincipalAttribute() throws Exception {
// custom principal-attribute
performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("aud"), KeycloakConfiguration.ALICE,
KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
getCallbackHandler( "test-webapp"));

// standard principal-attribute
performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("given_name"), KeycloakConfiguration.ALICE,
KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
getCallbackHandler("Alice"));

// invalid principal-attribute, logging in should still succeed
performAuthentication(getOidcConfigurationInputStreamWithPrincipalAttribute("invalid_claim"), KeycloakConfiguration.ALICE,
KeycloakConfiguration.ALICE_PASSWORD, true, HttpStatus.SC_MOVED_TEMPORARILY, getClientUrl(), CLIENT_PAGE_TEXT,
getCallbackHandler());
}

/*****************************************************************************************************************************************
* Tests for multi-tenancy.
*
Expand Down Expand Up @@ -411,16 +444,17 @@ private void testNonExistingUser(String username, String password, String tenant
}

private void loginToAppMultiTenancy(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText) throws Exception {
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
CallbackHandler callbackHandler) throws Exception {
try {
Map<String, Object> props = new HashMap<>();
OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());

OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
oidcFactory = new OidcMechanismFactory(oidcClientContext);
HttpServerAuthenticationMechanism mechanism;
mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());

HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler);

URI requestUri = new URI(getClientUrl());
TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
Expand Down Expand Up @@ -494,20 +528,25 @@ private void performTenantRequest(String username, String password, String tenan
}

private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText, String expectedScope, boolean checkInvalidScopeError) throws Exception {
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
String expectedScope, boolean checkInvalidScopeError) throws Exception {
performAuthentication(oidcConfig, username, password, loginToKeycloak, expectedDispatcherStatusCode, expectedLocation,
clientPageText, expectedScope, checkInvalidScopeError, getCallbackHandler(checkInvalidScopeError,
expectedScope, null));
}

private void performAuthentication(InputStream oidcConfig, String username, String password, boolean loginToKeycloak,
int expectedDispatcherStatusCode, String expectedLocation, String clientPageText,
String expectedScope, boolean checkInvalidScopeError,
CallbackHandler callbackHandler) throws Exception {
try {
Map<String, Object> props = new HashMap<>();
OidcClientConfiguration oidcClientConfiguration = OidcClientConfigurationBuilder.build(oidcConfig);
assertEquals(OidcClientConfiguration.RelativeUrlsUsed.NEVER, oidcClientConfiguration.getRelativeUrls());

OidcClientContext oidcClientContext = new OidcClientContext(oidcClientConfiguration);
oidcFactory = new OidcMechanismFactory(oidcClientContext);
HttpServerAuthenticationMechanism mechanism;
if (expectedScope == null) {
mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler());
} else {
mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, getCallbackHandler(true, expectedScope));
}
HttpServerAuthenticationMechanism mechanism = oidcFactory.createAuthenticationMechanism(OIDC_NAME, props, callbackHandler);

URI requestUri = new URI(getClientUrl());
TestingHttpServerRequest request = new TestingHttpServerRequest(null, requestUri);
Expand Down Expand Up @@ -640,7 +679,7 @@ private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm()
private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
String oidcConfig = "{\n" +
" \"client-id\" : \"" + CLIENT_ID + "\",\n" +
" \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "/" + "\",\n" +
" \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM_WITH_SCOPES + "/" + "\",\n" +
" \"public-client\" : \"false\",\n" +
" \"scope\" : \"" + scopeValue + "\",\n" +
" \"ssl-required\" : \"EXTERNAL\",\n" +
Expand All @@ -651,6 +690,20 @@ private InputStream getOidcConfigurationInputStreamWithScope(String scopeValue){
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}

private InputStream getOidcConfigurationInputStreamWithPrincipalAttribute(String principalAttributeValue) {
String oidcConfig = "{\n" +
" \"principal-attribute\" : \"" + principalAttributeValue + "\",\n" +
" \"resource\" : \"" + CLIENT_ID + "\",\n" +
" \"public-client\" : \"false\",\n" +
" \"provider-url\" : \"" + KEYCLOAK_CONTAINER.getAuthServerUrl() + "/realms/" + TEST_REALM + "\",\n" +
" \"ssl-required\" : \"EXTERNAL\",\n" +
" \"credentials\" : {\n" +
" \"secret\" : \"" + CLIENT_SECRET + "\"\n" +
" }\n" +
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}

static InputStream getTenantConfigWithAuthServerUrl(String tenant) {
String oidcConfig = "{\n" +
" \"realm\" : \"" + tenant + "\",\n" +
Expand Down

0 comments on commit ac72762

Please sign in to comment.