adorsys / keycloak-config-cli

Import YAML/JSON-formatted configuration files into Keycloak - Configuration as Code for Keycloak.
Apache License 2.0
734 stars 138 forks source link

Initial Credentials Causes Update #819

Open dwyerk opened 1 year ago

dwyerk commented 1 year ago

With #623 support for a new userLabel "initial" was added which prevents the credentials from being set if the user already exists. However, it can trigger a false update when only the password has changed.

This behavior can currently be seen in the ImportUsersIT.shouldUpdateRealmWithChangedClientUserPassword test. During the doImport call on line 189, the UserImportService will attempt to update the myinitialclientuser and this code will correctly add an empty list to the JSON:

            if (patchedUser.getCredentials() != null) {
                // do not override password, if userLabel is set "initial"
                List<CredentialRepresentation> userCredentials = patchedUser.getCredentials().stream()
                        .filter(credentialRepresentation -> !Objects.equals(
                                credentialRepresentation.getUserLabel(), USER_LABEL_FOR_INITIAL_CREDENTIAL
                        ))
                        .collect(Collectors.toList());
                patchedUser.setCredentials(userCredentials);
            }

However, the next condition will be true, and the service will try to update the user with keycloak:

            if (!CloneUtil.deepEquals(existingUser, patchedUser, "access")) {
                logger.debug("Update user '{}' in realm '{}'", userToImport.getUsername(), realmName);
                userRepository.updateUser(realmName, patchedUser);

But nothing has changed! The call to CloneUtil.deepEquals is returning true in this case but it should not.

This is a problem for those of us who want to use this feature to workaround the invalidpasswordhistorymessage issue where Keycloak won't accept keycloak-config-cli's update when there is a "Not Recently Used" password policy in place because it triggers an unnecessary update to the user which will fail.

dwyerk commented 1 year ago

Debugging into CloneUtils.deepEquals, we can see:

originJsonNode = {"id":"24c8403d-fe8b-4a10-b12d-85ea7279a495","createdTimestamp":1669679459001,"username":"myinitialclientuser","enabled":true,"totp":false,"emailVerified":false,"firstName":"My clientuser's firstname","lastName":"My clientuser's lastname","email":"myinitialclientuser@mail.de","disableableCredentialTypes":[],"requiredActions":[],"notBefore":0}

otherJsonNode = {"id":"24c8403d-fe8b-4a10-b12d-85ea7279a495","createdTimestamp":1669679459001,"username":"myinitialclientuser","enabled":true,"totp":false,"emailVerified":false,"firstName":"My clientuser's firstname","lastName":"My clientuser's lastname","email":"myinitialclientuser@mail.de","credentials":[],"disableableCredentialTypes":[],"requiredActions":[],"notBefore":0}

So the only difference is that patchedUser has "credentials":[] while existingUser does not.