Skip to content
New issue

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

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

Already on GitHub? # to your account

Fix/workflow failure #1250

Merged
merged 6 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ jobs:
run: ./mvnw ${MAVEN_CLI_OPTS} -Dkeycloak.version=${{ matrix.env.KEYCLOAK_VERSION }} -Dkeycloak.client.version=${{ matrix.env.KEYCLOAK_CLIENT_VERSION }} -Dkeycloak.dockerTagSuffix="-legacy" ${ADJUSTED_RESTEASY_VERSION} clean verify ${COMPATIBILITY_PROFILE}

lint-other-files:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4.2.2
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Fixed
- Fix Service Account User always triggers UPDATE USER event [#878](https://github.com/adorsys/keycloak-config-cli/issues/878)
### Added
- Publish charts with github pages [#941](https://github.com/adorsys/keycloak-config-cli/issues/941)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
import de.adorsys.keycloak.config.exception.KeycloakRepositoryException;
import de.adorsys.keycloak.config.provider.KeycloakProvider;
import de.adorsys.keycloak.config.util.ResponseUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.representations.idm.RealmRepresentation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;

import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.WebApplicationException;

Expand Down Expand Up @@ -57,7 +60,10 @@ public RealmResource getResource(String realmName) {
}

public RealmRepresentation get(String realmName) {
return getResource(realmName).toRepresentation();
final var realm = getResource(realmName).toRepresentation();
realm.setAttributes(ObjectUtils.firstNonNull(realm.getAttributes(), new HashMap<>()));
realm.setEventsEnabled(ObjectUtils.firstNonNull(realm.isEventsEnabled(), false));
return realm;
}

public void create(RealmRepresentation realm) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ public Optional<UserRepresentation> search(String realmName, String username) {
user = Optional.of(foundUsers.get(0));
}

// Retrieve the service account client id if it exists
user.ifPresent(u -> {
UserResource userResource = usersResource.get(u.getId());
UserRepresentation userRepresentation = userResource.toRepresentation();
u.setServiceAccountClientId(userRepresentation.getServiceAccountClientId());
});

return user;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
public class UserImportService {
private static final Logger logger = LoggerFactory.getLogger(UserImportService.class);

private static final String[] IGNORED_PROPERTIES_FOR_UPDATE = {"realmRoles", "clientRoles"};
private static final String[] IGNORED_PROPERTIES_FOR_UPDATE = {"realmRoles", "clientRoles", "serviceAccountClientId", "attributes"};
private static final String USER_LABEL_FOR_INITIAL_CREDENTIAL = "initial";

private final RealmRepository realmRepository;
Expand Down Expand Up @@ -159,6 +159,9 @@ private void updateUser(UserRepresentation existingUser) {
patchedUser.setCredentials(userCredentials.isEmpty() ? null : userCredentials);
}

logger.debug("Existing user: {}", existingUser);
logger.debug("Patched user: {}", patchedUser);

if (!CloneUtil.deepEquals(existingUser, patchedUser, "access")) {
logger.debug("Update user '{}' in realm '{}'", userToImport.getUsername(), realmName);
userRepository.updateUser(realmName, patchedUser);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public void doImport(RealmImport realmImport) {
RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm());
Map<String, String> customAttributes = existingRealm.getAttributes();


String importChecksum = realmImport.getChecksum();
String attributeKey = getCustomAttributeKey(realmImport);
customAttributes.put(attributeKey, importChecksum);
Expand All @@ -69,6 +70,7 @@ public boolean hasToBeUpdated(RealmImport realmImport) {
}
Map<String, String> customAttributes = existingRealm.getAttributes();


String readChecksum = customAttributes.get(getCustomAttributeKey(realmImport));
if (readChecksum == null) {
return true;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/de/adorsys/keycloak/config/util/CloneUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ public static <S, T> boolean deepEquals(S origin, T other, String... ignoredProp
logger.trace("objects.deepEquals: ret: {} | origin: {} | other: {} | ignoredProperties: {}",
ret, originJsonNode, otherJsonNode, ignoredProperties
);
if (!ret) {
logger.debug("Differences detected between origin and other:");
originJsonNode.fieldNames().forEachRemaining(fieldName -> {
JsonNode originValue = originJsonNode.get(fieldName);
JsonNode otherValue = otherJsonNode.get(fieldName);
if (!Objects.equals(originValue, otherValue)) {
logger.debug("Field '{}' is different: origin = {}, other = {}", fieldName, originValue, otherValue);
}
});
}

return ret;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,39 @@ void shouldNotUpdateUserWhenOnlyInitialPasswordChanges() throws IOException {
assertThat(token.getToken(), notNullValue());
}

@Test
@Order(16)
void shouldNotTriggerUpdateUserEventForServiceAccountUserWithoutChanges() throws IOException {
doImport("60.1_update_realm_add_clientl_with_service_account.json");

// Re-import the same configuration to check if UPDATE USER event is not triggered
doImport("60.2_update_realm_add_clientl_with_service_account.json");

RealmRepresentation realm = keycloakProvider.getInstance().realm(REALM_NAME).toRepresentation();
assertThat(realm.getRealm(), is(REALM_NAME));
assertThat(realm.isEnabled(), is(true));
assertThat(realm.isRegistrationAllowed(), is(true));
assertThat(realm.isRegistrationEmailAsUsername(), is(true));

final ClientRepresentation client = keycloakRepository.getClient(REALM_NAME, "technical-client");
assertThat(client.getClientId(), is("technical-client"));

UserRepresentation user = keycloakProvider.getInstance().realm(REALM_NAME)
.clients()
.get(client.getId())
.getServiceAccountUser();
assertThat(user.getUsername(), is("service-account-technical-client"));

List<String> clientLevelRoles = keycloakRepository.getServiceAccountUserClientLevelRoles(
REALM_NAME, client.getClientId(), "moped-client");
assertThat(clientLevelRoles, containsInAnyOrder("test_client_role", "other_test_client_role"));

List<String> keycloakNativeClientLevelRoles = keycloakRepository.getServiceAccountUserClientLevelRoles(
REALM_NAME, client.getClientId(), "realm-management");
assertThat(keycloakNativeClientLevelRoles, contains("view-realm"));

}

@Test
@Order(50)
void shouldUpdateUserWithEmailAsRegistration() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"enabled": true,
"realm": "realmWithUsers",
"registrationAllowed": true,
"registrationEmailAsUsername": true,
"roles": {
"client": {
"moped-client": [
{
"name": "test_client_role",
"description": "My updated moped-client role",
"composite": false,
"clientRole": true
},
{
"name": "other_test_client_role",
"description": "My changed other moped-client role",
"composite": false,
"clientRole": true
}
]
}
},
"clients": [
{
"clientId": "technical-client",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [],
"webOrigins": [],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"publicClient": false,
"frontchannelLogout": false,
"protocol": "openid-connect",
"defaultClientScopes": [
"role_list",
"roles"
],
"optionalClientScopes": []
},
{
"clientId": "moped-client",
"name": "moped-client",
"description": "Moped-Client",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"secret": "my-special-client-secret",
"bearerOnly": true,
"redirectUris": [],
"webOrigins": []
}
],
"users": [
{
"username": "service-account-technical-client",
"enabled": true,
"totp": false,
"emailVerified": false,
"serviceAccountClientId": "technical-client",
"clientRoles": {
"account": [
"manage-account",
"view-profile"
],
"moped-client": [
"test_client_role",
"other_test_client_role"
],
"realm-management": [
"view-realm"
]
},
"notBefore": 0
}
]
}
Loading