KEYCLOAK-9129 Don't expose Keycloak version in resource paths
This commit is contained in:
parent
b72fe79791
commit
3a36569e20
9 changed files with 193 additions and 27 deletions
|
@ -31,6 +31,7 @@ public class Version {
|
|||
public static String NAME_FULL;
|
||||
public static String NAME_HTML;
|
||||
public static String VERSION;
|
||||
public static String VERSION_KEYCLOAK;
|
||||
public static String RESOURCES_VERSION;
|
||||
public static String BUILD_TIME;
|
||||
public static String DEFAULT_PROFILE;
|
||||
|
@ -45,6 +46,7 @@ public class Version {
|
|||
Version.NAME_HTML = props.getProperty("name-html");
|
||||
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
|
||||
Version.VERSION = props.getProperty("version");
|
||||
Version.VERSION_KEYCLOAK = props.getProperty("version-keycloak");
|
||||
Version.BUILD_TIME = props.getProperty("build-time");
|
||||
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
||||
|
||||
|
|
|
@ -19,5 +19,6 @@ name=${product.name}
|
|||
name-full=${product.name.full}
|
||||
name-html=${product.name-html}
|
||||
version=${product.version}
|
||||
version-keycloak=${project.version}
|
||||
build-time=${product.build-time}
|
||||
default-profile=${product.default-profile}
|
|
@ -17,10 +17,14 @@
|
|||
|
||||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.jpa.entities.MigrationModelEntity;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -28,29 +32,62 @@ import javax.persistence.EntityManager;
|
|||
*/
|
||||
public class MigrationModelAdapter implements MigrationModel {
|
||||
protected EntityManager em;
|
||||
protected MigrationModelEntity latest;
|
||||
|
||||
private static final int RESOURCE_TAG_LENGTH = 5;
|
||||
private static final char[] RESOURCE_TAG_CHARSET = "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray();
|
||||
|
||||
public MigrationModelAdapter(EntityManager em) {
|
||||
this.em = em;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStoredVersion() {
|
||||
MigrationModelEntity entity = em.find(MigrationModelEntity.class, MigrationModelEntity.SINGLETON_ID);
|
||||
if (entity == null) return null;
|
||||
return entity.getVersion();
|
||||
return latest != null ? latest.getVersion() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourcesTag() {
|
||||
return latest != null ? latest.getId() : null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
TypedQuery<MigrationModelEntity> q = em.createNamedQuery("getLatest", MigrationModelEntity.class);
|
||||
q.setMaxResults(1);
|
||||
List<MigrationModelEntity> l = q.getResultList();
|
||||
if (l.isEmpty()) {
|
||||
latest = null;
|
||||
} else {
|
||||
latest = l.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStoredVersion(String version) {
|
||||
MigrationModelEntity entity = em.find(MigrationModelEntity.class, MigrationModelEntity.SINGLETON_ID);
|
||||
if (entity == null) {
|
||||
entity = new MigrationModelEntity();
|
||||
entity.setId(MigrationModelEntity.SINGLETON_ID);
|
||||
entity.setVersion(version);
|
||||
em.persist(entity);
|
||||
} else {
|
||||
entity.setVersion(version);
|
||||
em.flush();
|
||||
String resourceTag = createResourceTag();
|
||||
|
||||
// Make sure resource-tag is unique within current installation
|
||||
while (em.find(MigrationModelEntity.class, resourceTag) != null) {
|
||||
resourceTag = createResourceTag();
|
||||
}
|
||||
|
||||
MigrationModelEntity entity = new MigrationModelEntity();
|
||||
entity.setId(resourceTag);
|
||||
entity.setVersion(version);
|
||||
entity.setUpdatedTime(Time.currentTime());
|
||||
|
||||
em.persist(entity);
|
||||
|
||||
latest = entity;
|
||||
}
|
||||
|
||||
private String createResourceTag() {
|
||||
StringBuilder sb = new StringBuilder(RESOURCE_TAG_LENGTH);
|
||||
for (int i = 0; i < RESOURCE_TAG_LENGTH; i++) {
|
||||
sb.append(RESOURCE_TAG_CHARSET[new SecureRandom().nextInt(RESOURCE_TAG_CHARSET.length)]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@ import javax.persistence.AccessType;
|
|||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -30,6 +33,9 @@ import javax.persistence.Table;
|
|||
*/
|
||||
@Table(name="MIGRATION_MODEL")
|
||||
@Entity
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "getLatest", query = "select m from MigrationModelEntity m ORDER BY m.updatedTime DESC")
|
||||
})
|
||||
public class MigrationModelEntity {
|
||||
public static final String SINGLETON_ID = "SINGLETON";
|
||||
@Id
|
||||
|
@ -40,6 +46,9 @@ public class MigrationModelEntity {
|
|||
@Column(name="VERSION", length = 36)
|
||||
protected String version;
|
||||
|
||||
@Column(name="UPDATE_TIME")
|
||||
protected long updatedTime;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -56,6 +65,14 @@ public class MigrationModelEntity {
|
|||
this.version = version;
|
||||
}
|
||||
|
||||
public long getUpdateTime() {
|
||||
return updatedTime;
|
||||
}
|
||||
|
||||
public void setUpdatedTime(long updatedTime) {
|
||||
this.updatedTime = updatedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -202,4 +202,16 @@
|
|||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
<changeSet author="keycloak" id="8.0.0-resource-tag-support">
|
||||
<addColumn tableName="MIGRATION_MODEL">
|
||||
<column name="UPDATE_TIME" type="BIGINT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
|
||||
<createIndex tableName="MIGRATION_MODEL" indexName="IDX_UPDATE_TIME">
|
||||
<column name="UPDATE_TIME" type="BIGINT" />
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.migration.migrators.MigrateTo1_2_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo1_3_0;
|
||||
import org.keycloak.migration.migrators.MigrateTo1_4_0;
|
||||
|
@ -87,26 +88,28 @@ public class MigrationModelManager {
|
|||
};
|
||||
|
||||
public static void migrate(KeycloakSession session) {
|
||||
ModelVersion latest = migrations[migrations.length-1].getVersion();
|
||||
MigrationModel model = session.realms().getMigrationModel();
|
||||
ModelVersion stored = null;
|
||||
if (model.getStoredVersion() != null) {
|
||||
stored = new ModelVersion(model.getStoredVersion());
|
||||
if (latest.equals(stored)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (Migration m : migrations) {
|
||||
if (stored == null || stored.lessThan(m.getVersion())) {
|
||||
if (stored != null) {
|
||||
logger.debugf("Migrating older model to %s", m.getVersion());
|
||||
ModelVersion currentVersion = new ModelVersion(Version.VERSION_KEYCLOAK);
|
||||
ModelVersion latestUpdate = migrations[migrations.length-1].getVersion();
|
||||
ModelVersion databaseVersion = model.getStoredVersion() != null ? new ModelVersion(model.getStoredVersion()) : null;
|
||||
|
||||
if (databaseVersion == null || databaseVersion.lessThan(latestUpdate)) {
|
||||
for (Migration m : migrations) {
|
||||
if (databaseVersion == null || databaseVersion.lessThan(m.getVersion())) {
|
||||
if (databaseVersion != null) {
|
||||
logger.debugf("Migrating older model to %s", m.getVersion());
|
||||
}
|
||||
m.migrate(session);
|
||||
}
|
||||
m.migrate(session);
|
||||
}
|
||||
}
|
||||
|
||||
model.setStoredVersion(latest.toString());
|
||||
if (databaseVersion == null || databaseVersion.lessThan(currentVersion)) {
|
||||
model.setStoredVersion(currentVersion.toString());
|
||||
}
|
||||
|
||||
Version.RESOURCES_VERSION = model.getResourcesTag();
|
||||
}
|
||||
|
||||
public static final ModelVersion RHSSO_VERSION_7_0_KEYCLOAK_VERSION = new ModelVersion("1.9.8");
|
||||
|
|
|
@ -24,5 +24,6 @@ package org.keycloak.migration;
|
|||
*/
|
||||
public interface MigrationModel {
|
||||
String getStoredVersion();
|
||||
String getResourcesTag();
|
||||
void setStoredVersion(String version);
|
||||
}
|
||||
|
|
|
@ -16,11 +16,15 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.migration;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.ClientsResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.RoleResource;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.component.PrioritizedComponentModel;
|
||||
import org.keycloak.keys.KeyProvider;
|
||||
|
@ -57,6 +61,9 @@ import org.keycloak.testsuite.exportimport.ExportImportUtil;
|
|||
import org.keycloak.testsuite.runonserver.RunHelpers;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -64,6 +71,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
@ -257,6 +266,8 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||
|
||||
// MFA - Check that authentication flows were migrated as expected
|
||||
testOTPAuthenticatorsMigratedToConditionalFlow();
|
||||
|
||||
testResourceTag();
|
||||
}
|
||||
|
||||
private void testAdminClientUrls(RealmResource realm) {
|
||||
|
@ -736,4 +747,16 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
|||
testDecisionStrategySetOnResourceServer();
|
||||
}
|
||||
}
|
||||
|
||||
protected void testResourceTag() {
|
||||
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||
URI url = suiteContext.getAuthServerInfo().getUriBuilder().path("/auth").build();
|
||||
String response = SimpleHttp.doGet(url.toString(), client).asString();
|
||||
Matcher m = Pattern.compile("resources/([^/]*)/welcome").matcher(response);
|
||||
assertTrue(m.find());
|
||||
assertTrue(m.group(1).matches("[\\da-z]{5}"));
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package org.keycloak.testsuite.model;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.jpa.entities.MigrationModelEntity;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
|
||||
import org.keycloak.testsuite.runonserver.RunOnServerTest;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
|
||||
public class MigrationModelTest extends AbstractKeycloakTest {
|
||||
|
||||
@Deployment
|
||||
public static WebArchive deploy() {
|
||||
return RunOnServerDeployment.create(MigrationModelTest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
testingClient.server().run(session -> {
|
||||
String currentVersion = Version.VERSION_KEYCLOAK.split("-")[0];
|
||||
|
||||
JpaConnectionProvider p = session.getProvider(JpaConnectionProvider.class);
|
||||
EntityManager em = p.getEntityManager();
|
||||
|
||||
List<MigrationModelEntity> l = em.createQuery("select m from MigrationModelEntity m ORDER BY m.updatedTime DESC", MigrationModelEntity.class).getResultList();
|
||||
Assert.assertEquals(1, l.size());
|
||||
Assert.assertTrue(l.get(0).getId().matches("[\\da-z]{5}"));
|
||||
Assert.assertEquals(currentVersion, l.get(0).getVersion());
|
||||
|
||||
MigrationModel m = session.realms().getMigrationModel();
|
||||
Assert.assertEquals(currentVersion, m.getStoredVersion());
|
||||
Assert.assertEquals(m.getResourcesTag(), l.get(0).getId());
|
||||
|
||||
Time.setOffset(-5000);
|
||||
|
||||
session.realms().getMigrationModel().setStoredVersion("6.0.0");
|
||||
em.flush();
|
||||
|
||||
Time.setOffset(0);
|
||||
|
||||
l = em.createQuery("select m from MigrationModelEntity m ORDER BY m.updatedTime DESC", MigrationModelEntity.class).getResultList();
|
||||
Assert.assertEquals(2, l.size());
|
||||
Assert.assertTrue(l.get(0).getId().matches("[\\da-z]{5}"));
|
||||
Assert.assertEquals(currentVersion, l.get(0).getVersion());
|
||||
Assert.assertTrue(l.get(1).getId().matches("[\\da-z]{5}"));
|
||||
Assert.assertEquals("6.0.0", l.get(1).getVersion());
|
||||
|
||||
m = session.realms().getMigrationModel();
|
||||
Assert.assertEquals(l.get(0).getId(), m.getResourcesTag());
|
||||
Assert.assertEquals(currentVersion, m.getStoredVersion());
|
||||
|
||||
em.remove(l.get(1));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue