REALM_CLIENT attribute to recognize realm clients (#30433)

Closes #29413

Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com>
This commit is contained in:
Giuseppe Graziano 2024-06-19 10:22:13 +02:00 committed by GitHub
parent acf79b81c7
commit 24aa6e143d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 37 additions and 6 deletions

View file

@ -4,7 +4,8 @@ import type { TFunction } from "i18next";
/** /**
* Checks if a client is intended to be used for authenticating a to a realm. * Checks if a client is intended to be used for authenticating a to a realm.
*/ */
export const isRealmClient = (client: ClientRepresentation) => !client.protocol; export const isRealmClient = (client: ClientRepresentation): boolean =>
client.attributes?.["realm_client"] === true.toString();
/** /**
* Gets a human readable name for the specified protocol. * Gets a human readable name for the specified protocol.

View file

@ -181,4 +181,7 @@ public final class Constants {
// attribute name used in apps to mark that it is an admin console and its azp is allowed // attribute name used in apps to mark that it is an admin console and its azp is allowed
public static final String SECURITY_ADMIN_CONSOLE_ATTR = "security.admin.console"; public static final String SECURITY_ADMIN_CONSOLE_ATTR = "security.admin.console";
//attribute name used to mark a client as realm client
public static final String REALM_CLIENT = "realm_client";
} }

View file

@ -21,6 +21,7 @@ import static java.util.Optional.ofNullable;
import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets; import static org.keycloak.models.utils.StripSecretsUtils.stripSecrets;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.otp.OTPApplicationProvider; import org.keycloak.authentication.otp.OTPApplicationProvider;
import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationProviderFactory; import org.keycloak.authorization.AuthorizationProviderFactory;
@ -737,6 +738,8 @@ public class ModelToRepresentation {
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout()); rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType()); rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
rep.getAttributes().put(Constants.REALM_CLIENT, String.valueOf(isRealmClient(clientModel.getClientId(), clientModel.getRealm(), session)));
// adding the secret if non public or bearer only // adding the secret if non public or bearer only
if (clientModel.isBearerOnly() || clientModel.isPublicClient()) { if (clientModel.isBearerOnly() || clientModel.isPublicClient()) {
rep.setSecret(null); rep.setSecret(null);
@ -778,6 +781,23 @@ public class ModelToRepresentation {
return rep; return rep;
} }
private static boolean isRealmClient(String clientId, RealmModel realm, KeycloakSession session) {
final String realmClientSuffix = "-realm";
if (clientId == null) {
return false;
}
if (Constants.BROKER_SERVICE_CLIENT_ID.equals(clientId)) {
return true;
}
if (Config.getAdminRealm().equals(realm.getName())) {
return clientId.endsWith(realmClientSuffix) && session.realms().getRealmByName(clientId.substring(0, clientId.length() - realmClientSuffix.length())) != null;
}
else {
return Constants.REALM_MANAGEMENT_CLIENT_ID.equals(clientId);
}
}
public static IdentityProviderRepresentation toBriefRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) { public static IdentityProviderRepresentation toBriefRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation(); IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation();

View file

@ -96,6 +96,13 @@ public class ClientTest extends AbstractAdminTest {
Assert.assertNames(realm.clients().findAll(), "account", "account-console", "realm-management", "security-admin-console", "broker", Constants.ADMIN_CLI_CLIENT_ID); Assert.assertNames(realm.clients().findAll(), "account", "account-console", "realm-management", "security-admin-console", "broker", Constants.ADMIN_CLI_CLIENT_ID);
} }
@Test
public void getRealmClients() {
assertTrue(realm.clients().findAll().stream().filter(client-> client.getAttributes().get(Constants.REALM_CLIENT).equals("true"))
.map(ClientRepresentation::getClientId)
.allMatch(clientId -> clientId.equals(Constants.REALM_MANAGEMENT_CLIENT_ID) || clientId.equals(Constants.BROKER_SERVICE_CLIENT_ID) || clientId.endsWith("-realm")));
}
private ClientRepresentation createClient() { private ClientRepresentation createClient() {
return createClient(null); return createClient(null);
} }
@ -122,7 +129,7 @@ public class ClientTest extends AbstractAdminTest {
return rep; return rep;
} }
private ClientRepresentation createClientNonPublic() { private ClientRepresentation createClientNonPublic() {
ClientRepresentation rep = new ClientRepresentation(); ClientRepresentation rep = new ClientRepresentation();
rep.setClientId("my-app"); rep.setClientId("my-app");
@ -142,7 +149,7 @@ public class ClientTest extends AbstractAdminTest {
return rep; return rep;
} }
@Test @Test
public void createClientVerifyWithSecret() { public void createClientVerifyWithSecret() {
String id = createClientNonPublic().getId(); String id = createClientNonPublic().getId();
@ -442,14 +449,14 @@ public class ClientTest extends AbstractAdminTest {
assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME), Collections.singletonList(role), ResourceType.REALM_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.CREATE, AdminEventPaths.roleResourceCompositesPath(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME), Collections.singletonList(role), ResourceType.REALM_ROLE);
assertThat(realm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME).getRoleComposites().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()), assertThat(realm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME).getRoleComposites().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()),
hasItem(role.getName())); hasItem(role.getName()));
realm.clients().get(id).roles().deleteRole("test"); realm.clients().get(id).roles().deleteRole("test");
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.clientRoleResourcePath(id, "test"), ResourceType.CLIENT_ROLE); assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.clientRoleResourcePath(id, "test"), ResourceType.CLIENT_ROLE);
assertThat(realm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME).getRoleComposites().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()), assertThat(realm.roles().get(Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + REALM_NAME).getRoleComposites().stream().map(RoleRepresentation::getName).collect(Collectors.toSet()),
not(hasItem(role))); not(hasItem(role)));
} }
@ -558,7 +565,7 @@ public class ClientTest extends AbstractAdminTest {
realm.clients().get(id).registerNode(Collections.singletonMap("node", "foo#")); realm.clients().get(id).registerNode(Collections.singletonMap("node", "foo#"));
} }
@Test @Test
public void nodes() { public void nodes() {
testingClient.testApp().clearAdminActions(); testingClient.testApp().clearAdminActions();