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_FULL;
|
||||||
public static String NAME_HTML;
|
public static String NAME_HTML;
|
||||||
public static String VERSION;
|
public static String VERSION;
|
||||||
|
public static String VERSION_KEYCLOAK;
|
||||||
public static String RESOURCES_VERSION;
|
public static String RESOURCES_VERSION;
|
||||||
public static String BUILD_TIME;
|
public static String BUILD_TIME;
|
||||||
public static String DEFAULT_PROFILE;
|
public static String DEFAULT_PROFILE;
|
||||||
|
@ -45,6 +46,7 @@ public class Version {
|
||||||
Version.NAME_HTML = props.getProperty("name-html");
|
Version.NAME_HTML = props.getProperty("name-html");
|
||||||
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
|
Version.DEFAULT_PROFILE = props.getProperty("default-profile");
|
||||||
Version.VERSION = props.getProperty("version");
|
Version.VERSION = props.getProperty("version");
|
||||||
|
Version.VERSION_KEYCLOAK = props.getProperty("version-keycloak");
|
||||||
Version.BUILD_TIME = props.getProperty("build-time");
|
Version.BUILD_TIME = props.getProperty("build-time");
|
||||||
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
||||||
|
|
||||||
|
|
|
@ -19,5 +19,6 @@ name=${product.name}
|
||||||
name-full=${product.name.full}
|
name-full=${product.name.full}
|
||||||
name-html=${product.name-html}
|
name-html=${product.name-html}
|
||||||
version=${product.version}
|
version=${product.version}
|
||||||
|
version-keycloak=${project.version}
|
||||||
build-time=${product.build-time}
|
build-time=${product.build-time}
|
||||||
default-profile=${product.default-profile}
|
default-profile=${product.default-profile}
|
|
@ -17,10 +17,14 @@
|
||||||
|
|
||||||
package org.keycloak.models.jpa;
|
package org.keycloak.models.jpa;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.migration.MigrationModel;
|
import org.keycloak.migration.MigrationModel;
|
||||||
import org.keycloak.models.jpa.entities.MigrationModelEntity;
|
import org.keycloak.models.jpa.entities.MigrationModelEntity;
|
||||||
|
|
||||||
import javax.persistence.EntityManager;
|
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>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -28,29 +32,62 @@ import javax.persistence.EntityManager;
|
||||||
*/
|
*/
|
||||||
public class MigrationModelAdapter implements MigrationModel {
|
public class MigrationModelAdapter implements MigrationModel {
|
||||||
protected EntityManager em;
|
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) {
|
public MigrationModelAdapter(EntityManager em) {
|
||||||
this.em = em;
|
this.em = em;
|
||||||
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getStoredVersion() {
|
public String getStoredVersion() {
|
||||||
MigrationModelEntity entity = em.find(MigrationModelEntity.class, MigrationModelEntity.SINGLETON_ID);
|
return latest != null ? latest.getVersion() : null;
|
||||||
if (entity == null) return null;
|
}
|
||||||
return entity.getVersion();
|
|
||||||
|
@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
|
@Override
|
||||||
public void setStoredVersion(String version) {
|
public void setStoredVersion(String version) {
|
||||||
MigrationModelEntity entity = em.find(MigrationModelEntity.class, MigrationModelEntity.SINGLETON_ID);
|
String resourceTag = createResourceTag();
|
||||||
if (entity == null) {
|
|
||||||
entity = new MigrationModelEntity();
|
// Make sure resource-tag is unique within current installation
|
||||||
entity.setId(MigrationModelEntity.SINGLETON_ID);
|
while (em.find(MigrationModelEntity.class, resourceTag) != null) {
|
||||||
|
resourceTag = createResourceTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationModelEntity entity = new MigrationModelEntity();
|
||||||
|
entity.setId(resourceTag);
|
||||||
entity.setVersion(version);
|
entity.setVersion(version);
|
||||||
|
entity.setUpdatedTime(Time.currentTime());
|
||||||
|
|
||||||
em.persist(entity);
|
em.persist(entity);
|
||||||
} else {
|
|
||||||
entity.setVersion(version);
|
latest = entity;
|
||||||
em.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -30,6 +33,9 @@ import javax.persistence.Table;
|
||||||
*/
|
*/
|
||||||
@Table(name="MIGRATION_MODEL")
|
@Table(name="MIGRATION_MODEL")
|
||||||
@Entity
|
@Entity
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name = "getLatest", query = "select m from MigrationModelEntity m ORDER BY m.updatedTime DESC")
|
||||||
|
})
|
||||||
public class MigrationModelEntity {
|
public class MigrationModelEntity {
|
||||||
public static final String SINGLETON_ID = "SINGLETON";
|
public static final String SINGLETON_ID = "SINGLETON";
|
||||||
@Id
|
@Id
|
||||||
|
@ -40,6 +46,9 @@ public class MigrationModelEntity {
|
||||||
@Column(name="VERSION", length = 36)
|
@Column(name="VERSION", length = 36)
|
||||||
protected String version;
|
protected String version;
|
||||||
|
|
||||||
|
@Column(name="UPDATE_TIME")
|
||||||
|
protected long updatedTime;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +65,14 @@ public class MigrationModelEntity {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getUpdateTime() {
|
||||||
|
return updatedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedTime(long updatedTime) {
|
||||||
|
this.updatedTime = updatedTime;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -202,4 +202,16 @@
|
||||||
|
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
|
<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>
|
</databaseChangeLog>
|
|
@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.Version;
|
||||||
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;
|
||||||
|
@ -87,26 +88,28 @@ public class MigrationModelManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void migrate(KeycloakSession session) {
|
public static void migrate(KeycloakSession session) {
|
||||||
ModelVersion latest = migrations[migrations.length-1].getVersion();
|
|
||||||
MigrationModel model = session.realms().getMigrationModel();
|
MigrationModel model = session.realms().getMigrationModel();
|
||||||
ModelVersion stored = null;
|
|
||||||
if (model.getStoredVersion() != null) {
|
|
||||||
stored = new ModelVersion(model.getStoredVersion());
|
|
||||||
if (latest.equals(stored)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
for (Migration m : migrations) {
|
||||||
if (stored == null || stored.lessThan(m.getVersion())) {
|
if (databaseVersion == null || databaseVersion.lessThan(m.getVersion())) {
|
||||||
if (stored != null) {
|
if (databaseVersion != null) {
|
||||||
logger.debugf("Migrating older model to %s", m.getVersion());
|
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");
|
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 {
|
public interface MigrationModel {
|
||||||
String getStoredVersion();
|
String getStoredVersion();
|
||||||
|
String getResourcesTag();
|
||||||
void setStoredVersion(String version);
|
void setStoredVersion(String version);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.migration;
|
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.hamcrest.Matchers;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.admin.client.resource.ClientsResource;
|
import org.keycloak.admin.client.resource.ClientsResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.RoleResource;
|
import org.keycloak.admin.client.resource.RoleResource;
|
||||||
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.common.constants.KerberosConstants;
|
import org.keycloak.common.constants.KerberosConstants;
|
||||||
import org.keycloak.component.PrioritizedComponentModel;
|
import org.keycloak.component.PrioritizedComponentModel;
|
||||||
import org.keycloak.keys.KeyProvider;
|
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.runonserver.RunHelpers;
|
||||||
import org.keycloak.testsuite.util.OAuthClient;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -64,6 +71,8 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
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
|
// MFA - Check that authentication flows were migrated as expected
|
||||||
testOTPAuthenticatorsMigratedToConditionalFlow();
|
testOTPAuthenticatorsMigratedToConditionalFlow();
|
||||||
|
|
||||||
|
testResourceTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testAdminClientUrls(RealmResource realm) {
|
private void testAdminClientUrls(RealmResource realm) {
|
||||||
|
@ -736,4 +747,16 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
testDecisionStrategySetOnResourceServer();
|
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