KEYCLOAK-18069 Migration of client policies JSON from Keycloak 13

This commit is contained in:
mposolda 2021-05-31 08:20:00 +02:00 committed by Marek Posolda
parent aac0b6ec5f
commit 070c68e18a
9 changed files with 2888 additions and 21 deletions

View file

@ -18,8 +18,13 @@
package org.keycloak.representations.idm; package org.keycloak.representations.idm;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.jboss.logging.Logger;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -33,6 +38,9 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RealmRepresentation { public class RealmRepresentation {
private static final Logger logger = Logger.getLogger(RealmRepresentation.class);
protected String id; protected String id;
protected String realm; protected String realm;
protected String displayName; protected String displayName;
@ -144,8 +152,11 @@ public class RealmRepresentation {
// Client Policies/Profiles // Client Policies/Profiles
protected ClientProfilesRepresentation clientProfiles; @JsonProperty("clientProfiles")
protected ClientPoliciesRepresentation clientPolicies; protected JsonNode clientProfiles;
@JsonProperty("clientPolicies")
protected JsonNode clientPolicies;
protected List<UserRepresentation> users; protected List<UserRepresentation> users;
protected List<UserRepresentation> federatedUsers; protected List<UserRepresentation> federatedUsers;
@ -1181,20 +1192,44 @@ public class RealmRepresentation {
// Client Policies/Profiles // Client Policies/Profiles
public ClientProfilesRepresentation getClientProfiles() { @JsonIgnore
return clientProfiles; public ClientProfilesRepresentation getParsedClientProfiles() {
try {
if (clientProfiles == null) return null;
return JsonSerialization.mapper.convertValue(clientProfiles, ClientProfilesRepresentation.class);
} catch (IllegalArgumentException ioe) {
logger.warnf("Failed to deserialize client profiles in the realm %s. Fallback to return empty profiles. Details: %s", realm, ioe.getMessage());
return null;
}
} }
public void setClientProfiles(ClientProfilesRepresentation clientProfiles) { @JsonIgnore
this.clientProfiles = clientProfiles; public void setParsedClientProfiles(ClientProfilesRepresentation clientProfiles) {
if (clientProfiles == null) {
this.clientProfiles = null;
return;
}
this.clientProfiles = JsonSerialization.mapper.convertValue(clientProfiles, JsonNode.class);
} }
public ClientPoliciesRepresentation getClientPolicies() { @JsonIgnore
return clientPolicies; public ClientPoliciesRepresentation getParsedClientPolicies() {
try {
if (clientPolicies == null) return null;
return JsonSerialization.mapper.convertValue(clientPolicies, ClientPoliciesRepresentation.class);
} catch (IllegalArgumentException ioe) {
logger.warnf("Failed to deserialize client policies in the realm %s. Fallback to return empty profiles. Details: %s", realm, ioe.getMessage());
return null;
}
} }
public void setClientPolicies(ClientPoliciesRepresentation clientPolicies) { @JsonIgnore
this.clientPolicies = clientPolicies; public void setParsedClientPolicies(ClientPoliciesRepresentation clientPolicies) {
if (clientPolicies == null) {
this.clientPolicies = null;
return;
}
this.clientPolicies = JsonSerialization.mapper.convertValue(clientPolicies, JsonNode.class);
} }
public String getBrowserFlow() { public String getBrowserFlow() {

View file

@ -27,6 +27,7 @@
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/> <module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
<module name="org.keycloak.keycloak-common"/> <module name="org.keycloak.keycloak-common"/>
<module name="org.bouncycastle" /> <module name="org.bouncycastle" />
<module name="org.jboss.logging"/>
<module name="javax.api"/> <module name="javax.api"/>
<module name="javax.activation.api"/> <module name="javax.activation.api"/>
<module name="sun.jdk" optional="true" /> <module name="sun.jdk" optional="true" />

View file

@ -23,6 +23,7 @@ import java.util.regex.Pattern;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.migration.migrators.MigrateTo12_0_0; import org.keycloak.migration.migrators.MigrateTo12_0_0;
import org.keycloak.migration.migrators.MigrateTo14_0_0;
import org.keycloak.migration.migrators.MigrateTo1_2_0; import org.keycloak.migration.migrators.MigrateTo1_2_0;
import org.keycloak.migration.migrators.MigrateTo1_3_0; import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0; import org.keycloak.migration.migrators.MigrateTo1_4_0;
@ -94,7 +95,8 @@ public class MigrationModelManager {
new MigrateTo8_0_2(), new MigrateTo8_0_2(),
new MigrateTo9_0_0(), new MigrateTo9_0_0(),
new MigrateTo9_0_4(), new MigrateTo9_0_4(),
new MigrateTo12_0_0() new MigrateTo12_0_0(),
new MigrateTo14_0_0()
}; };
public static void migrate(KeycloakSession session) { public static void migrate(KeycloakSession session) {

View file

@ -0,0 +1,56 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientProfilesRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyException;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MigrateTo14_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("14.0.0");
@Override
public void migrate(KeycloakSession session) {
session.realms()
.getRealmsStream()
.forEach(realm -> migrateRealm(session, realm));
}
private void migrateRealm(KeycloakSession session, RealmModel realm) {
try {
session.clientPolicy().updateClientProfiles(realm, new ClientProfilesRepresentation());
session.clientPolicy().updateClientPolicies(realm, new ClientPoliciesRepresentation());
} catch (ClientPolicyException cpe) {
throw new ModelException("Exception during migration client profiles or client policies", cpe);
}
}
@Override
public ModelVersion getVersion() {
return VERSION;
}
}

View file

@ -202,6 +202,7 @@ public class ClientPoliciesUtil {
try { try {
return JsonSerialization.readValue(json, ClientProfilesRepresentation.class); return JsonSerialization.readValue(json, ClientProfilesRepresentation.class);
} catch (IOException ioe) { } catch (IOException ioe) {
throw new ClientPolicyException(ioe.getMessage()); throw new ClientPolicyException(ioe.getMessage());
} }
} }

View file

@ -184,18 +184,19 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
public void updateRealmModelFromRepresentation(RealmModel realm, RealmRepresentation rep) { public void updateRealmModelFromRepresentation(RealmModel realm, RealmRepresentation rep) {
logger.tracev("LOAD PROFILE POLICIES ON IMPORTED REALM :: realm = {0}", realm.getName()); logger.tracev("LOAD PROFILE POLICIES ON IMPORTED REALM :: realm = {0}", realm.getName());
if (rep.getClientProfiles() != null) { if (rep.getParsedClientProfiles() != null) {
try { try {
updateClientProfiles(realm, rep.getClientProfiles()); updateClientProfiles(realm, rep.getParsedClientProfiles());
} catch (ClientPolicyException e) { } catch (ClientPolicyException e) {
logger.warnv("VALIDATE SERIALIZE IMPORTED REALM PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); logger.warnv("VALIDATE SERIALIZE IMPORTED REALM PROFILES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail());
throw new RuntimeException("Failed to update client profiles", e); throw new RuntimeException("Failed to update client profiles", e);
} }
} }
if (rep.getClientPolicies() != null) { ClientPoliciesRepresentation clientPolicies = rep.getParsedClientPolicies();
if (clientPolicies != null) {
try { try {
updateClientPolicies(realm, rep.getClientPolicies()); updateClientPolicies(realm, clientPolicies);
} catch (ClientPolicyException e) { } catch (ClientPolicyException e) {
logger.warnv("VALIDATE SERIALIZE IMPORTED REALM POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail()); logger.warnv("VALIDATE SERIALIZE IMPORTED REALM POLICIES FAILED :: error = {0}, error detail = {1}", e.getError(), e.getErrorDetail());
throw new RuntimeException("Failed to update client policies", e); throw new RuntimeException("Failed to update client policies", e);
@ -280,10 +281,10 @@ public class DefaultClientPolicyManager implements ClientPolicyManager {
try { try {
// client profiles that filter out global profiles.. // client profiles that filter out global profiles..
ClientProfilesRepresentation filteredOutProfiles = getClientProfiles(realm, false); ClientProfilesRepresentation filteredOutProfiles = getClientProfiles(realm, false);
rep.setClientProfiles(filteredOutProfiles); rep.setParsedClientProfiles(filteredOutProfiles);
ClientPoliciesRepresentation filteredOutPolicies = getClientPolicies(realm); ClientPoliciesRepresentation filteredOutPolicies = getClientPolicies(realm);
rep.setClientPolicies(filteredOutPolicies); rep.setParsedClientPolicies(filteredOutPolicies);
} catch (ClientPolicyException cpe) { } catch (ClientPolicyException cpe) {
throw new IllegalStateException("Exception during export client profiles or client policies", cpe); throw new IllegalStateException("Exception during export client profiles or client policies", cpe);
} }

View file

@ -428,15 +428,15 @@ public class ClientPoliciesLoadUpdateTest extends AbstractClientPoliciesTest {
// Get the realm and assert that expected policies and profiles are present // Get the realm and assert that expected policies and profiles are present
RealmResource testRealm = realmsResouce().realm("test"); RealmResource testRealm = realmsResouce().realm("test");
RealmRepresentation realmRep = testRealm.toRepresentation(); RealmRepresentation realmRep = testRealm.toRepresentation();
assertExpectedProfiles(realmRep.getClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile")); assertExpectedProfiles(realmRep.getParsedClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile"));
assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getClientPolicies()); assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getParsedClientPolicies());
// Update the realm // Update the realm
testRealm.update(realmRep); testRealm.update(realmRep);
// Test the realm again // Test the realm again
realmRep = testRealm.toRepresentation(); realmRep = testRealm.toRepresentation();
assertExpectedProfiles(realmRep.getClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile")); assertExpectedProfiles(realmRep.getParsedClientProfiles(), null, Arrays.asList("ordinal-test-profile", "lack-of-builtin-field-test-profile"));
assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getClientPolicies()); assertExpectedPolicies(Arrays.asList("new-policy", "lack-of-builtin-field-test-policy"), realmRep.getParsedClientPolicies());
} }
} }

View file

@ -0,0 +1,70 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.testsuite.migration;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.keycloak.exportimport.util.ImportUtils;
import org.keycloak.representations.idm.ClientPoliciesRepresentation;
import org.keycloak.representations.idm.ClientProfilesRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.util.WaitUtils;
import org.keycloak.testsuite.utils.io.IOUtil;
import org.keycloak.util.JsonSerialization;
/**
* This is test only for migration of client policies from Keycloak 13. As the format JSON format of client policies changed between Keycloak 13 and 14
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@AuthServerContainerExclude(value = {AuthServerContainerExclude.AuthServer.REMOTE, AuthServerContainerExclude.AuthServer.QUARKUS}, details = "It works locally for Quarkus, but failing on CI for unknown reason")
public class JsonFileImport1301MigrationClientPoliciesTest extends AbstractJsonFileImportMigrationTest {
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
Map<String, RealmRepresentation> reps = null;
try {
reps = ImportUtils.getRealmsFromStream(JsonSerialization.mapper, IOUtil.class.getResourceAsStream("/migration-test/migration-realm-13.0.1-client-policies.json"));
} catch (IOException e) {
throw new RuntimeException(e);
}
for (RealmRepresentation rep : reps.values()) {
testRealms.add(rep);
}
}
@Test
public void migration13_0_1_Test() throws Exception {
RealmRepresentation testRealm = adminClient.realms().realm("test").toRepresentation();
// Stick to null for now. No support for proper migration from Keycloak 13 as client policies was preview and JSON format was changed significantly
Assert.assertTrue(testRealm.getParsedClientProfiles().getProfiles().isEmpty());
Assert.assertTrue(testRealm.getParsedClientPolicies().getPolicies().isEmpty());
ClientProfilesRepresentation clientProfiles = adminClient.realms().realm("test").clientPoliciesProfilesResource().getProfiles(false);
Assert.assertTrue(clientProfiles.getProfiles().isEmpty());
ClientPoliciesRepresentation clientPolicies = adminClient.realms().realm("test").clientPoliciesPoliciesResource().getPolicies();
Assert.assertTrue(clientPolicies.getPolicies().isEmpty());
}
}