Skip to content

Commit

Permalink
KEYCLOAK-9129 Don't expose Keycloak version in resource paths
Browse files Browse the repository at this point in the history
  • Loading branch information
stianst committed Nov 15, 2019
1 parent b72fe79 commit 3a36569
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 27 deletions.
2 changes: 2 additions & 0 deletions common/src/main/java/org/keycloak/common/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();

Expand Down
1 change: 1 addition & 0 deletions common/src/main/resources/keycloak-version.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,77 @@

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:[email protected]">Bill Burke</a>
* @version $Revision: 1 $
*/
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 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);
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 {
entity.setVersion(version);
em.flush();
latest = l.get(0);
}
}

@Override
public void setStoredVersion(String version) {
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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@
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:[email protected]">Bill Burke</a>
* @version $Revision: 1 $
*/
@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
Expand All @@ -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;
}
Expand All @@ -56,6 +65,14 @@ public void setVersion(String version) {
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;
Expand Down
14 changes: 13 additions & 1 deletion model/jpa/src/main/resources/META-INF/jpa-changelog-8.0.0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@
*/
public interface MigrationModel {
String getStoredVersion();
String getResourcesTag();
void setStoredVersion(String version);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,13 +61,18 @@
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;
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;
Expand Down Expand Up @@ -257,6 +266,8 @@ protected void testMigrationTo8_0_0() {

// MFA - Check that authentication flows were migrated as expected
testOTPAuthenticatorsMigratedToConditionalFlow();

testResourceTag();
}

private void testAdminClientUrls(RealmResource realm) {
Expand Down Expand Up @@ -736,4 +747,16 @@ protected void testMigrationTo7_x(boolean supportedAuthzServices) {
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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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));
});
}

}

0 comments on commit 3a36569

Please sign in to comment.