Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Keycloak 25.0.1 #1052

Merged
merged 8 commits into from
Jun 25, 2024
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
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Used in docker-compose
# shellcheck disable=SC2034
KEYCLOAK_VERSION=24.0.5
KEYCLOAK_VERSION=25.0.1
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
- KEYCLOAK_VERSION: 22.0.4
- KEYCLOAK_VERSION: 23.0.7
- KEYCLOAK_VERSION: 24.0.5
- KEYCLOAK_VERSION: 25.0.1
steps:
- uses: actions/checkout@v4.1.7
with:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Updated CI to use Keycloak 25.0.1
- Identity Providers are now updated using the name of policies, scopes and resources

### Fixed
- Importing more than 10 subgroups into a realm
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ FROM ${BUILDER_IMAGE} AS BUILDER

WORKDIR /app/

ARG KEYCLOAK_VERSION=24.0.5
ARG KEYCLOAK_VERSION=25.0.1
ARG MAVEN_CLI_OPTS="-ntp -B"

COPY .mvn .mvn
Expand Down
4 changes: 2 additions & 2 deletions contrib/example-config/benchmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9466,9 +9466,9 @@ requiredActions:
defaultAction: false
priority: 10
config: {}
- alias: terms_and_conditions
- alias: TERMS_AND_CONDITIONS
name: "Terms and Conditions"
providerId: terms_and_conditions
providerId: TERMS_AND_CONDITIONS
enabled: false
defaultAction: false
priority: 20
Expand Down
4 changes: 2 additions & 2 deletions contrib/example-config/moped.json
Original file line number Diff line number Diff line change
Expand Up @@ -940,9 +940,9 @@
"config": {}
},
{
"alias": "terms_and_conditions",
"alias": "TERMS_AND_CONDITIONS",
"name": "Terms and Conditions",
"providerId": "terms_and_conditions",
"providerId": "TERMS_AND_CONDITIONS",
"enabled": false,
"defaultAction": false,
"priority": 20,
Expand Down
2 changes: 1 addition & 1 deletion contrib/scripts/generate-export-realm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ docker run -d --rm \
"quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}" \
start-dev

while ! docker exec keycloak-export bash -c '/opt/keycloak/bin/kcadm.sh config credentials --server http://$HOSTNAME:8080/auth --realm master --user $KEYCLOAK_USER --password $KEYCLOAK_PASSWORD'; do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need an user and password anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mme-flendly,

the problem is, that we don't have a user in the env-vars and the Keycloak-API does not take empty values any more. This is only a helper script to generate the realm exports for the test suite as well as changing some files towards the new version so it's not part of the deliverable.

while ! docker exec keycloak-export bash -c '/opt/keycloak/bin/kcadm.sh config credentials --server http://$HOSTNAME:8080/auth --realm master'; do
sleep 2
done

Expand Down
57 changes: 55 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<keycloak.version>24.0.5</keycloak.version>
<keycloak.version>25.0.1</keycloak.version>

<checkstyle-plugin.version>3.3.1</checkstyle-plugin.version>
<checkstyle.version>10.17.0</checkstyle.version>
Expand Down Expand Up @@ -163,6 +163,11 @@
<artifactId>spring-boot-loader</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>

<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
Expand Down Expand Up @@ -627,7 +632,7 @@
<version>${maven-replacer.version}</version>
<executions>
<execution>
<id>replace-pre-keycloak22</id>
<id>replace-pre-keycloak19</id>
Mme-adorsys marked this conversation as resolved.
Show resolved Hide resolved
<phase>generate-sources</phase>
<goals>
<goal>replace</goal>
Expand Down Expand Up @@ -659,6 +664,22 @@ import org.keycloak.representations.userprofile.config.UPConfig;</token>
<token>return groupResource.getSubGroups\(0, Integer.MAX_VALUE, false\);</token>
<value>return groupResource.toRepresentation().getSubGroups();</value>
</replacement>
<replacement>
<token>if \(userProfile == null\)</token>
<value>if (userProfile == null || userProfile.isEmpty())</value>
</replacement>
<replacement>
<token>private UPConfig userProfile;</token>
<value>private Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile;</value>
</replacement>
<replacement>
<token>public void setUserProfile\(UPConfig userProfile\)</token>
<value>public void setUserProfile(Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile)</value>
</replacement>
<replacement>
<token>public UPConfig getUserProfile\(\)</token>
<value>public Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; getUserProfile()</value>
</replacement>
</replacements>
</configuration>
</plugin>
Expand Down Expand Up @@ -736,6 +757,22 @@ import org.keycloak.representations.userprofile.config.UPConfig;</token>
<token>return groupResource.getSubGroups\(0, Integer.MAX_VALUE, false\);</token>
<value>return groupResource.toRepresentation().getSubGroups();</value>
</replacement>
<replacement>
<token>if \(userProfile == null\)</token>
<value>if (userProfile == null || userProfile.isEmpty())</value>
</replacement>
<replacement>
<token>private UPConfig userProfile;</token>
<value>private Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile;</value>
</replacement>
<replacement>
<token>public void setUserProfile\(UPConfig userProfile\)</token>
<value>public void setUserProfile(Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile)</value>
</replacement>
<replacement>
<token>public UPConfig getUserProfile\(\)</token>
<value>public Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; getUserProfile()</value>
</replacement>
</replacements>
</configuration>
</plugin>
Expand Down Expand Up @@ -798,6 +835,22 @@ import org.keycloak.representations.userprofile.config.UPConfig;</token>
<token>return groupResource.getSubGroups\(0, Integer.MAX_VALUE, false\);</token>
<value>return groupResource.toRepresentation().getSubGroups();</value>
</replacement>
<replacement>
<token>if \(userProfile == null\)</token>
<value>if (userProfile == null || userProfile.isEmpty())</value>
</replacement>
<replacement>
<token>private UPConfig userProfile;</token>
<value>private Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile;</value>
</replacement>
<replacement>
<token>public void setUserProfile\(UPConfig userProfile\)</token>
<value>public void setUserProfile(Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; userProfile)</value>
</replacement>
<replacement>
<token>public UPConfig getUserProfile\(\)</token>
<value>public Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; getUserProfile()</value>
</replacement>
</replacements>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonSetter;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.userprofile.config.UPConfig;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
Expand All @@ -34,7 +35,7 @@
public class RealmImport extends RealmRepresentation {
private List<AuthenticationFlowImport> authenticationFlowImports;

private Map<String, List<Map<String, Object>>> userProfile;
private UPConfig userProfile;

private Map<String, Map<String, String>> messageBundles;

Expand All @@ -56,7 +57,7 @@ public void setAuthenticationFlowImports(List<AuthenticationFlowImport> authenti

@SuppressWarnings("unused")
@JsonSetter("userProfile")
public void setUserProfile(Map<String, List<Map<String, Object>>> userProfile) {
public void setUserProfile(UPConfig userProfile) {
this.userProfile = userProfile;
}

Expand All @@ -70,7 +71,7 @@ public void setMessageBundles(Map<String, Map<String, String>> messageBundles) {
this.messageBundles = messageBundles;
}

public Map<String, List<Map<String, Object>>> getUserProfile() {
public UPConfig getUserProfile() {
return userProfile;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.time.Duration;
import java.util.function.Supplier;

import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.client.Entity;
Expand All @@ -54,16 +55,17 @@ public class KeycloakProvider implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(KeycloakProvider.class);

private final KeycloakConfigProperties properties;
private final ResteasyClient resteasyClient;
private final Supplier<ResteasyClient> resteasyClientSupplier;

private Keycloak keycloak;
private ResteasyClient resteasyClient;

private String version;

@Autowired
private KeycloakProvider(KeycloakConfigProperties properties) {
this.properties = properties;
this.resteasyClient = ResteasyUtil.getClient(
this.resteasyClientSupplier = () -> ResteasyUtil.getClient(
!this.properties.isSslVerify(),
this.properties.getHttpProxy(),
this.properties.getConnectTimeout(),
Expand All @@ -72,7 +74,8 @@ private KeycloakProvider(KeycloakConfigProperties properties) {
}

public Keycloak getInstance() {
if (keycloak == null || keycloak.isClosed()) {
if (keycloak == null || resteasyClient == null || keycloak.isClosed() || resteasyClient.isClosed()) {
resteasyClient = resteasyClientSupplier.get();
keycloak = createKeycloak();

checkServerVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,24 @@ public void createAuthorizationResource(String realmName, String id, ResourceRep

public void updateAuthorizationResource(String realmName, String id, ResourceRepresentation resource) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().resources().resource(resource.getId()).update(resource);
String resourceId = getResourceId(clientResource, resource.getName());
clientResource.authorization().resources().resource(resourceId).update(resource);
}

public void removeAuthorizationResource(String realmName, String id, String resourceId) {
public void removeAuthorizationResource(String realmName, String id, String resourceName) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().resources().resource(resourceId).remove();
String resourceId = getResourceId(clientResource, resourceName);
if (resourceId != null) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No warning, error or log when the resourceId was not found?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion idempotency requires not existing entries not to throw errors when they get deleted. I am not sure if this needs a debug log or not but it doesn't need a warn log.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everytime there is an error with keycloak-config-cli we need to switch to debug log. I think there should be more warn logs in case of something unexpected.

Copy link
Collaborator Author

@jonasvoelcker jonasvoelcker Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bohmber,

you are free to push changes and open pull requests as well. 😉

clientResource.authorization().resources().resource(resourceId).remove();
}
}

private String getResourceId(ClientResource clientResource, String resourceName) {
return clientResource.authorization().resources().resources().stream()
.filter(resource -> resourceName.equals(resource.getName()))
.findFirst()
.map(ResourceRepresentation::getId)
.orElse(null);
}

public void addAuthorizationScope(String realmName, String id, String name) {
Expand All @@ -200,12 +212,24 @@ public void addAuthorizationScope(String realmName, String id, String name) {

public void updateAuthorizationScope(String realmName, String id, ScopeRepresentation scope) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().scopes().scope(scope.getId()).update(scope);
String scopeId = getScopeId(clientResource, scope.getName());
clientResource.authorization().scopes().scope(scopeId).update(scope);
}

public void removeAuthorizationScope(String realmName, String id, String scopeId) {
public void removeAuthorizationScope(String realmName, String id, String scopeName) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().scopes().scope(scopeId).remove();
String scopeId = getScopeId(clientResource, scopeName);
if (scopeId != null) {
clientResource.authorization().scopes().scope(scopeId).remove();
}
}

private String getScopeId(ClientResource clientResource, String scopeName) {
return clientResource.authorization().scopes().scopes().stream()
.filter(scope -> scopeName.equals(scope.getName()))
.findFirst()
.map(ScopeRepresentation::getId)
.orElse(null);
}

public void createAuthorizationPolicy(String realmName, String id, PolicyRepresentation policy) {
Expand All @@ -218,12 +242,24 @@ public void createAuthorizationPolicy(String realmName, String id, PolicyReprese

public void updateAuthorizationPolicy(String realmName, String id, PolicyRepresentation policy) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().policies().policy(policy.getId()).update(policy);
String policyId = getPolicyId(clientResource, policy.getName());
clientResource.authorization().policies().policy(policyId).update(policy);
}

public void removeAuthorizationPolicy(String realmName, String id, String policyId) {
public void removeAuthorizationPolicy(String realmName, String id, String policyName) {
ClientResource clientResource = getResourceById(realmName, id);
clientResource.authorization().policies().policy(policyId).remove();
String policyId = getPolicyId(clientResource, policyName);
if (policyId != null) {
clientResource.authorization().policies().policy(policyId).remove();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, no warn, error or log

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explanation above 😉

}
}

private String getPolicyId(ClientResource clientResource, String policyName) {
return clientResource.authorization().policies().policies().stream()
.filter(policy -> policyName.equals(policy.getName()))
.findFirst()
.map(PolicyRepresentation::getId)
.orElse(null);
}

public void addScopeMapping(String realmName, String clientId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -86,7 +87,7 @@ public void createExecutionFlow(
logger.trace("Create non-top-level-flow in realm '{}' and top-level-flow '{}'", realmName, topLevelFlowAlias);

AuthenticationManagementResource flowsResource = authenticationFlowRepository.getFlowResources(realmName);
flowsResource.addExecutionFlow(topLevelFlowAlias, executionFlowData);
flowsResource.addExecutionFlow(topLevelFlowAlias, new HashMap<>(executionFlowData));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the order of the executionFlowData relevant here? If it is, then a LinkedHashMap that preserves the order would be more suitable here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @thomasdarimont, as the source type is also HashMap, I suggest that the order is not important ;)

}

public void updateExecutionFlow(
Expand Down Expand Up @@ -136,7 +137,7 @@ public void createSubFlowExecution(
realmName, subFlowAlias);

AuthenticationManagementResource flowsResource = authenticationFlowRepository.getFlowResources(realmName);
flowsResource.addExecution(subFlowAlias, executionData);
flowsResource.addExecution(subFlowAlias, new HashMap<>(executionData));

logger.trace("Created flow-execution in realm '{}' and non-top-level-flow '{}'",
realmName, subFlowAlias);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ private void removeAuthorizationResource(
existingClientAuthorizationResource.getName(), getClientIdentifier(client), realmName
);
clientRepository.removeAuthorizationResource(
realmName, client.getId(), existingClientAuthorizationResource.getId()
realmName, client.getId(), existingClientAuthorizationResource.getName()
);
}

Expand Down Expand Up @@ -430,7 +430,7 @@ private void removeAuthorizationScope(
logger.debug("Remove authorization scope '{}' for client '{}' in realm '{}'",
existingClientAuthorizationScope.getName(), getClientIdentifier(client), realmName);

clientRepository.removeAuthorizationScope(realmName, client.getId(), existingClientAuthorizationScope.getId());
clientRepository.removeAuthorizationScope(realmName, client.getId(), existingClientAuthorizationScope.getName());
}

private void createOrUpdateAuthorizationPolicies(
Expand Down Expand Up @@ -519,7 +519,7 @@ private void removeAuthorizationPolicy(

try {
clientRepository.removeAuthorizationPolicy(
realmName, client.getId(), existingClientAuthorizationPolicy.getId()
realmName, client.getId(), existingClientAuthorizationPolicy.getName()
);
} catch (NotFoundException ignored) {
// policies got deleted if linked resources are deleted, too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void doImport(RealmImport realmImport) {

private String buildUserProfileConfigurationString(RealmImport realmImport) {
var userProfile = realmImport.getUserProfile();
if (userProfile == null || userProfile.isEmpty()) {
if (userProfile == null) {
return null;
}
return JsonUtil.toJson(userProfile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import de.adorsys.keycloak.config.extensions.GithubActionsExtension;
import de.adorsys.keycloak.config.model.RealmImport;
import de.adorsys.keycloak.config.provider.KeycloakImportProvider;
import de.adorsys.keycloak.config.provider.KeycloakProvider;
import de.adorsys.keycloak.config.service.RealmImportService;
import de.adorsys.keycloak.config.test.util.KeycloakAuthentication;
import de.adorsys.keycloak.config.test.util.KeycloakRepository;
Expand Down
Loading
Loading