keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java 56(+56 -0)
model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java 65(+64 -1)
model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java 48(+48 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java 17(+13 -4)
testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java 77(+77 -0)
testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json 10(+10 -0)
testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml 42(+42 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java 43(+43 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java 5(+5 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java 29(+15 -14)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java 7(+0 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java 25(+2 -23)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java 48(+48 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java 73(+54 -19)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java 132(+132 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java 86(+83 -3)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json 10(+10 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/jetty-web.xml 46(+46 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml 57(+57 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json 64(+64 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json 49(+49 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl 10(+10 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl 45(+45 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/examples-realm.json 1795(+0 -1795)
testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/examples-realm-bak.json 1797(+0 -1797)
testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json 52(+52 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java 13(+7 -6)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java 1(+1 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
index 52d47df..edfd392 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.keys.infinispan;
import java.security.PublicKey;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
@@ -32,6 +33,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.keys.PublicKeyLoader;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;
@@ -51,13 +53,17 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
private final int minTimeBetweenRequests ;
+ private Set<String> invalidations = new HashSet<>();
+
public InfinispanPublicKeyStorageProvider(KeycloakSession session, Cache<String, PublicKeysEntry> keys, Map<String, FutureTask<PublicKeysEntry>> tasksInProgress, int minTimeBetweenRequests) {
this.session = session;
this.keys = keys;
this.tasksInProgress = tasksInProgress;
this.minTimeBetweenRequests = minTimeBetweenRequests;
+ session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
}
+
@Override
public void clearCache() {
keys.clear();
@@ -65,6 +71,56 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
cluster.notify(InfinispanPublicKeyStorageProviderFactory.KEYS_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
}
+
+ void addInvalidation(String cacheKey) {
+ this.invalidations.add(cacheKey);
+ }
+
+
+ protected KeycloakTransaction getAfterTransaction() {
+ return new KeycloakTransaction() {
+
+ @Override
+ public void begin() {
+ }
+
+ @Override
+ public void commit() {
+ runInvalidations();
+ }
+
+ @Override
+ public void rollback() {
+ runInvalidations();
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+ };
+ }
+
+
+ protected void runInvalidations() {
+ ClusterProvider cluster = session.getProvider(ClusterProvider.class);
+
+ for (String cacheKey : invalidations) {
+ keys.remove(cacheKey);
+ cluster.notify(cacheKey, PublicKeyStorageInvalidationEvent.create(cacheKey), true);
+ }
+ }
+
+
@Override
public PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
// Check if key is in cache
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
index 8f2e321..42a73fc 100644
--- a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/InfinispanPublicKeyStorageProviderFactory.java
@@ -30,9 +30,14 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.keys.PublicKeyStorageProvider;
import org.keycloak.keys.PublicKeyStorageSpi;
import org.keycloak.keys.PublicKeyStorageProviderFactory;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
+import org.keycloak.provider.ProviderEvent;
+import org.keycloak.provider.ProviderEventListener;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -45,7 +50,7 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
public static final String KEYS_CLEAR_CACHE_EVENTS = "KEYS_CLEAR_CACHE_EVENTS";
- private Cache<String, PublicKeysEntry> keysCache;
+ private volatile Cache<String, PublicKeysEntry> keysCache;
private final Map<String, FutureTask<PublicKeysEntry>> tasksInProgress = new ConcurrentHashMap<>();
@@ -64,6 +69,15 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
this.keysCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
+ cluster.registerListener(ClusterProvider.ALL, (ClusterEvent event) -> {
+
+ if (event instanceof PublicKeyStorageInvalidationEvent) {
+ PublicKeyStorageInvalidationEvent invalidationEvent = (PublicKeyStorageInvalidationEvent) event;
+ keysCache.remove(invalidationEvent.getCacheKey());
+ }
+
+ });
+
cluster.registerListener(KEYS_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
keysCache.clear();
@@ -82,6 +96,55 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
@Override
public void postInit(KeycloakSessionFactory factory) {
+ factory.register(new ProviderEventListener() {
+
+ @Override
+ public void onEvent(ProviderEvent event) {
+ if (keysCache == null) {
+ return;
+ }
+
+ SessionAndKeyHolder cacheKey = getCacheKeyToInvalidate(event);
+ if (cacheKey != null) {
+ log.debugf("Invalidating %s from keysCache", cacheKey);
+ InfinispanPublicKeyStorageProvider provider = (InfinispanPublicKeyStorageProvider) cacheKey.session.getProvider(PublicKeyStorageProvider.class, getId());
+ provider.addInvalidation(cacheKey.cacheKey);
+ }
+ }
+
+ });
+ }
+
+ private SessionAndKeyHolder getCacheKeyToInvalidate(ProviderEvent event) {
+ if (event instanceof RealmModel.ClientUpdatedEvent) {
+ RealmModel.ClientUpdatedEvent eventt = (RealmModel.ClientUpdatedEvent) event;
+ String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getUpdatedClient().getRealm().getId(), eventt.getUpdatedClient().getId());
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ } else if (event instanceof RealmModel.ClientRemovedEvent) {
+ RealmModel.ClientRemovedEvent eventt = (RealmModel.ClientRemovedEvent) event;
+ String cacheKey = PublicKeyStorageUtils.getClientModelCacheKey(eventt.getClient().getRealm().getId(), eventt.getClient().getId());
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ } else if (event instanceof RealmModel.IdentityProviderUpdatedEvent) {
+ RealmModel.IdentityProviderUpdatedEvent eventt = (RealmModel.IdentityProviderUpdatedEvent) event;
+ String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getUpdatedIdentityProvider().getInternalId());
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ } else if (event instanceof RealmModel.IdentityProviderRemovedEvent) {
+ RealmModel.IdentityProviderRemovedEvent eventt = (RealmModel.IdentityProviderRemovedEvent) event;
+ String cacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(eventt.getRealm().getId(), eventt.getRemovedIdentityProvider().getInternalId());
+ return new SessionAndKeyHolder(eventt.getKeycloakSession(), cacheKey);
+ } else {
+ return null;
+ }
+ }
+
+ private class SessionAndKeyHolder {
+ private final KeycloakSession session;
+ private final String cacheKey;
+
+ public SessionAndKeyHolder(KeycloakSession session, String cacheKey) {
+ this.session = session;
+ this.cacheKey = cacheKey;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java
new file mode 100644
index 0000000..1afcf42
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/keys/infinispan/PublicKeyStorageInvalidationEvent.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 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.keys.infinispan;
+
+import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PublicKeyStorageInvalidationEvent extends InvalidationEvent {
+
+ private String cacheKey;
+
+ public static PublicKeyStorageInvalidationEvent create(String cacheKey) {
+ PublicKeyStorageInvalidationEvent event = new PublicKeyStorageInvalidationEvent();
+ event.cacheKey = cacheKey;
+ return event;
+ }
+
+ @Override
+ public String getId() {
+ return cacheKey;
+ }
+
+ public String getCacheKey() {
+ return cacheKey;
+ }
+
+ @Override
+ public String toString() {
+ return "PublicKeyStorageInvalidationEvent [ " + cacheKey + " ]";
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index b3f58b4..830da5f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -960,23 +960,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
for (IdentityProviderEntity entity: entities) {
- IdentityProviderModel identityProviderModel = new IdentityProviderModel();
- identityProviderModel.setProviderId(entity.getProviderId());
- identityProviderModel.setAlias(entity.getAlias());
- identityProviderModel.setDisplayName(entity.getDisplayName());
-
- identityProviderModel.setInternalId(entity.getInternalId());
- Map<String, String> config = entity.getConfig();
- Map<String, String> copy = new HashMap<>();
- copy.putAll(config);
- identityProviderModel.setConfig(copy);
- identityProviderModel.setEnabled(entity.isEnabled());
- identityProviderModel.setTrustEmail(entity.isTrustEmail());
- identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
- identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
- identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
- identityProviderModel.setStoreToken(entity.isStoreToken());
- identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+ IdentityProviderModel identityProviderModel = entityToModel(entity);
identityProviders.add(identityProviderModel);
}
@@ -984,6 +968,27 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
return Collections.unmodifiableList(identityProviders);
}
+ private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+ identityProviderModel.setProviderId(entity.getProviderId());
+ identityProviderModel.setAlias(entity.getAlias());
+ identityProviderModel.setDisplayName(entity.getDisplayName());
+
+ identityProviderModel.setInternalId(entity.getInternalId());
+ Map<String, String> config = entity.getConfig();
+ Map<String, String> copy = new HashMap<>();
+ copy.putAll(config);
+ identityProviderModel.setConfig(copy);
+ identityProviderModel.setEnabled(entity.isEnabled());
+ identityProviderModel.setTrustEmail(entity.isTrustEmail());
+ identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+ identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
+ identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
+ identityProviderModel.setStoreToken(entity.isStoreToken());
+ identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+ return identityProviderModel;
+ }
+
@Override
public IdentityProviderModel getIdentityProviderByAlias(String alias) {
for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
@@ -1024,8 +1029,28 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
public void removeIdentityProviderByAlias(String alias) {
for (IdentityProviderEntity entity : realm.getIdentityProviders()) {
if (entity.getAlias().equals(alias)) {
+
em.remove(entity);
em.flush();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() {
+
+ @Override
+ public RealmModel getRealm() {
+ return RealmAdapter.this;
+ }
+
+ @Override
+ public IdentityProviderModel getRemovedIdentityProvider() {
+ return entityToModel(entity);
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+
}
}
}
@@ -1048,6 +1073,24 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
em.flush();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderUpdatedEvent() {
+
+ @Override
+ public RealmModel getRealm() {
+ return RealmAdapter.this;
+ }
+
+ @Override
+ public IdentityProviderModel getUpdatedIdentityProvider() {
+ return identityProvider;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
}
@Override
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d581710..429d389 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -775,23 +775,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
for (IdentityProviderEntity entity: entities) {
- IdentityProviderModel identityProviderModel = new IdentityProviderModel();
-
- identityProviderModel.setProviderId(entity.getProviderId());
- identityProviderModel.setAlias(entity.getAlias());
- identityProviderModel.setDisplayName(entity.getDisplayName());
- identityProviderModel.setInternalId(entity.getInternalId());
- Map<String, String> config = entity.getConfig();
- Map<String, String> copy = new HashMap<>();
- copy.putAll(config);
- identityProviderModel.setConfig(copy);
- identityProviderModel.setEnabled(entity.isEnabled());
- identityProviderModel.setTrustEmail(entity.isTrustEmail());
- identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
- identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
- identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
- identityProviderModel.setStoreToken(entity.isStoreToken());
- identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+ IdentityProviderModel identityProviderModel = entityToModel(entity);
identityProviders.add(identityProviderModel);
}
@@ -799,6 +783,27 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
return Collections.unmodifiableList(identityProviders);
}
+ private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+ identityProviderModel.setProviderId(entity.getProviderId());
+ identityProviderModel.setAlias(entity.getAlias());
+ identityProviderModel.setDisplayName(entity.getDisplayName());
+ identityProviderModel.setInternalId(entity.getInternalId());
+ Map<String, String> config = entity.getConfig();
+ Map<String, String> copy = new HashMap<>();
+ copy.putAll(config);
+ identityProviderModel.setConfig(copy);
+ identityProviderModel.setEnabled(entity.isEnabled());
+ identityProviderModel.setTrustEmail(entity.isTrustEmail());
+ identityProviderModel.setAuthenticateByDefault(entity.isAuthenticateByDefault());
+ identityProviderModel.setFirstBrokerLoginFlowId(entity.getFirstBrokerLoginFlowId());
+ identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
+ identityProviderModel.setStoreToken(entity.isStoreToken());
+ identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
+ return identityProviderModel;
+ }
+
@Override
public IdentityProviderModel getIdentityProviderByAlias(String alias) {
for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
@@ -837,6 +842,25 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
if (entity.getAlias().equals(alias)) {
realm.getIdentityProviders().remove(entity);
updateRealm();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderRemovedEvent() {
+
+ @Override
+ public RealmModel getRealm() {
+ return RealmAdapter.this;
+ }
+
+ @Override
+ public IdentityProviderModel getRemovedIdentityProvider() {
+ return entityToModel(entity);
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
+
break;
}
}
@@ -860,6 +884,24 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
updateRealm();
+
+ session.getKeycloakSessionFactory().publish(new RealmModel.IdentityProviderUpdatedEvent() {
+
+ @Override
+ public RealmModel getRealm() {
+ return RealmAdapter.this;
+ }
+
+ @Override
+ public IdentityProviderModel getUpdatedIdentityProvider() {
+ return identityProvider;
+ }
+
+ @Override
+ public KeycloakSession getKeycloakSession() {
+ return session;
+ }
+ });
}
@Override
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 4409b9d..3149f50 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -67,6 +67,18 @@ public interface RealmModel extends RoleContainerModel {
KeycloakSession getKeycloakSession();
}
+ interface IdentityProviderUpdatedEvent extends ProviderEvent {
+ RealmModel getRealm();
+ IdentityProviderModel getUpdatedIdentityProvider();
+ KeycloakSession getKeycloakSession();
+ }
+
+ interface IdentityProviderRemovedEvent extends ProviderEvent {
+ RealmModel getRealm();
+ IdentityProviderModel getRemovedIdentityProvider();
+ KeycloakSession getKeycloakSession();
+ }
+
String getId();
String getName();
diff --git a/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
new file mode 100644
index 0000000..52eb7db
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/keys/PublicKeyStorageUtils.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 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.keys;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PublicKeyStorageUtils {
+
+ public static String getClientModelCacheKey(String realmId, String clientUuid) {
+ return realmId + "::client::" + clientUuid;
+ }
+
+ public static String getIdpModelCacheKey(String realmId, String idpInternalId) {
+ return realmId + "::idp::" + idpInternalId;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 0ef5276..951d03a 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -66,6 +66,7 @@ import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
@@ -92,6 +93,7 @@ public class SAMLEndpoint {
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
public static final String SAML_ASSERTION = "SAML_ASSERTION";
+ public static final String SAML_IDP_INITIATED_CLIENT_ID = "SAML_IDP_INITIATED_CLIENT_ID";
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
protected RealmModel realm;
protected EventBuilder event;
@@ -130,7 +132,7 @@ public class SAMLEndpoint {
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
- return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
+ return new RedirectBinding().execute(samlRequest, samlResponse, relayState, null);
}
@@ -141,7 +143,29 @@ public class SAMLEndpoint {
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
- return new PostBinding().execute(samlRequest, samlResponse, relayState);
+ return new PostBinding().execute(samlRequest, samlResponse, relayState, null);
+ }
+
+ @Path("clients/{client_id}")
+ @GET
+ public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+ @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+ @QueryParam(GeneralConstants.RELAY_STATE) String relayState,
+ @PathParam("client_id") String clientId) {
+ return new RedirectBinding().execute(samlRequest, samlResponse, relayState, clientId);
+ }
+
+
+ /**
+ */
+ @Path("clients/{client_id}")
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
+ @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
+ @FormParam(GeneralConstants.RELAY_STATE) String relayState,
+ @PathParam("client_id") String clientId) {
+ return new PostBinding().execute(samlRequest, samlResponse, relayState, clientId);
}
protected abstract class Binding {
@@ -194,12 +218,12 @@ public class SAMLEndpoint {
return new HardcodedKeyLocator(keys);
}
- public Response execute(String samlRequest, String samlResponse, String relayState) {
+ public Response execute(String samlRequest, String samlResponse, String relayState, String clientId) {
event = new EventBuilder(realm, session, clientConnection);
Response response = basicChecks(samlRequest, samlResponse);
if (response != null) return response;
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
- else return handleSamlResponse(samlResponse, relayState);
+ else return handleSamlResponse(samlResponse, relayState, clientId);
}
protected Response handleSamlRequest(String samlRequest, String relayState) {
@@ -304,7 +328,7 @@ public class SAMLEndpoint {
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
}
- protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
+ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState, String clientId) {
try {
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
@@ -316,6 +340,9 @@ public class SAMLEndpoint {
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion);
+ if (clientId != null && ! clientId.trim().isEmpty()) {
+ identity.getContextData().put(SAML_IDP_INITIATED_CLIENT_ID, clientId);
+ }
identity.setUsername(subjectNameID.getValue());
@@ -369,7 +396,7 @@ public class SAMLEndpoint {
- public Response handleSamlResponse(String samlResponse, String relayState) {
+ public Response handleSamlResponse(String samlResponse, String relayState, String clientId) {
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
// validate destination
@@ -390,7 +417,7 @@ public class SAMLEndpoint {
}
}
if (statusResponse instanceof ResponseType) {
- return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
+ return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState, clientId);
} else {
// todo need to check that it is actually a LogoutResponse
diff --git a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
index 811dacf..d4507e9 100644
--- a/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
+++ b/services/src/main/java/org/keycloak/keys/loader/PublicKeyStorageManager.java
@@ -22,6 +22,7 @@ import java.security.PublicKey;
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.keys.PublicKeyStorageProvider;
+import org.keycloak.keys.PublicKeyStorageUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -36,27 +37,20 @@ public class PublicKeyStorageManager {
PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
- String modelKey = getModelKey(client);
+ String modelKey = PublicKeyStorageUtils.getClientModelCacheKey(client.getRealm().getId(), client.getId());
ClientPublicKeyLoader loader = new ClientPublicKeyLoader(session, client);
return keyStorage.getPublicKey(modelKey, kid, loader);
}
- private static String getModelKey(ClientModel client) {
- return client.getRealm().getId() + "::client::" + client.getId();
- }
-
public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) {
String kid = input.getHeader().getKeyId();
PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
- String modelKey = getModelKey(realm, idpConfig);
+ String modelKey = PublicKeyStorageUtils.getIdpModelCacheKey(realm.getId(), idpConfig.getInternalId());
OIDCIdentityProviderPublicKeyLoader loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig);
return keyStorage.getPublicKey(modelKey, kid, loader);
}
- private static String getModelKey(RealmModel realm, OIDCIdentityProviderConfig idpConfig) {
- return realm.getId() + "::idp::" + idpConfig.getInternalId();
- }
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 14c5503..c404ef8 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -611,12 +611,29 @@ public class SamlService extends AuthorizationEndpointBase {
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
+ ClientSessionModel clientSession = createClientSessionForIdpInitiatedSso(this.session, this.realm, client, relayState);
+
+ return newBrowserAuthentication(clientSession, false, false);
+ }
+
+ /**
+ * Creates a client session object for SAML IdP-initiated SSO session.
+ * The session takes the parameters from from client definition,
+ * namely binding type and redirect URL.
+ *
+ * @param session KC session
+ * @param realm Realm to create client session in
+ * @param client Client to create client session for
+ * @param relayState Optional relay state - free field as per SAML specification
+ * @return
+ */
+ public static ClientSessionModel createClientSessionForIdpInitiatedSso(KeycloakSession session, RealmModel realm, ClientModel client, String relayState) {
String bindingType = SamlProtocol.SAML_POST_BINDING;
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
}
- String redirect = null;
+ String redirect;
if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
} else {
@@ -640,8 +657,7 @@ public class SamlService extends AuthorizationEndpointBase {
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
}
- return newBrowserAuthentication(clientSession, false, false);
-
+ return clientSession;
}
@POST
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index a8c4cc4..b1f4587 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -19,6 +19,7 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
@@ -30,6 +31,7 @@ import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.provider.IdentityProviderMapper;
+import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
@@ -54,8 +56,11 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlService;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.AccessToken;
+import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ServicesLogger;
@@ -87,6 +92,8 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
@@ -255,7 +262,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
public Response authenticated(BrokeredIdentityContext context) {
IdentityProviderModel identityProviderConfig = context.getIdpConfig();
- ParsedCodeContext parsedCode = parseClientSessionCode(context.getCode());
+ final ParsedCodeContext parsedCode;
+ if (context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID) != null) {
+ parsedCode = samlIdpInitiatedSSO((String) context.getContextData().get(SAMLEndpoint.SAML_IDP_INITIATED_CLIENT_ID));
+ } else {
+ parsedCode = parseClientSessionCode(context.getCode());
+ }
if (parsedCode.response != null) {
return parsedCode.response;
}
@@ -696,6 +708,53 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return ParsedCodeContext.response(staleCodeError);
}
+ /**
+ * If there is a client whose SAML IDP-initiated SSO URL name is set to the
+ * given {@code clientUrlName}, creates a fresh client session for that
+ * client and returns a {@link ParsedCodeContext} object with that session.
+ * Otherwise returns "client not found" response.
+ *
+ * @param clientUrlName
+ * @return see description
+ */
+ private ParsedCodeContext samlIdpInitiatedSSO(final String clientUrlName) {
+ event.event(EventType.LOGIN);
+ CacheControlUtil.noBackButtonCacheControlHeader();
+ Optional<ClientModel> oClient = this.realmModel.getClients().stream()
+ .filter(c -> Objects.equals(c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME), clientUrlName))
+ .findFirst();
+
+ if (! oClient.isPresent()) {
+ event.error(Errors.CLIENT_NOT_FOUND);
+ return ParsedCodeContext.response(redirectToErrorPage(Messages.CLIENT_NOT_FOUND));
+ }
+
+ ClientSessionModel clientSession = SamlService.createClientSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
+
+ return ParsedCodeContext.clientSessionCode(new ClientSessionCode(session, this.realmModel, clientSession));
+ }
+
+ /**
+ * Returns {@code true} if the client session is defined for the given code
+ * in the current session and for the current realm.
+ * Does <b>not</b> check the session validity. To obtain client session if
+ * and only if it exists and is valid, use {@link ClientSessionCode#parse}.
+ *
+ * @param code
+ * @return
+ */
+ protected boolean isClientSessionRegistered(String code) {
+ if (code == null) {
+ return false;
+ }
+
+ try {
+ return ClientSessionCode.getClientSession(code, this.session, this.realmModel) != null;
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
+
private Response checkAccountManagementFailedLinking(ClientSessionModel clientSession, String error, Object... parameters) {
if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
index cf0f55e..fd35caf 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -35,6 +35,8 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
+
+import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
@@ -63,13 +65,20 @@ public class TestingOIDCEndpointsApplicationResource {
@NoCache
public Map<String, String> generateKeys() {
try {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
- generator.initialize(2048);
- clientData.setSigningKeyPair(generator.generateKeyPair());
- } catch (NoSuchAlgorithmException e) {
+ KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
+ clientData.setSigningKeyPair(keyPair);
+ } catch (Exception e) {
throw new BadRequestException("Error generating signing keypair", e);
}
+ return getKeysAsPem();
+ }
+
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/get-keys-as-pem")
+ public Map<String, String> getKeysAsPem() {
String privateKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPrivate());
String publicKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPublic());
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
new file mode 100644
index 0000000..d9d031a
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
@@ -0,0 +1,56 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-test-apps</artifactId>
+ <version>2.4.1.Final-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>keycloak-test-app-profile-jee</artifactId>
+
+ <name>Keycloak Test App Profile JEE</name>
+ <description/>
+
+ <packaging>war</packaging>
+
+ <properties>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>app-profile-jee</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.wildfly.plugins</groupId>
+ <artifactId>wildfly-maven-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md b/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md
new file mode 100644
index 0000000..71bb42f
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/README.md
@@ -0,0 +1,10 @@
+You need to create a client in Keycloak. The configuration options when creating the client should be:
+
+* Client ID: You choose
+* Access Type: confidential
+* Root URL: Root URL for where you're hosting the application (for example http://localhost:8080)
+* Valie Redirect URIs: /app-profile-jee/*
+* Base URL: /app-profile-jee/
+* Admin URL: /app-profile-jee/
+
+Then, build the WAR with Maven and install as per the Adapter configuration for your server as described in the Keycloak documentation.
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java
new file mode 100644
index 0000000..2f863b9
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/java/org/keycloak/quickstart/profilejee/Controller.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.quickstart.profilejee;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.constants.ServiceUrlConstants;
+import org.keycloak.representations.IDToken;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * Controller simplifies access to the server environment from the JSP.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
+ */
+public class Controller {
+
+ public void handleLogout(HttpServletRequest req) throws ServletException {
+ if (req.getParameter("logout") != null) {
+ req.logout();
+ }
+ }
+
+ public boolean isLoggedIn(HttpServletRequest req) {
+ return getSession(req) != null;
+ }
+
+ public boolean showToken(HttpServletRequest req) {
+ return req.getParameter("showToken") != null;
+ }
+
+ public IDToken getIDToken(HttpServletRequest req) {
+ return getSession(req).getIdToken();
+ }
+
+ public String getAccountUri(HttpServletRequest req) {
+ KeycloakSecurityContext session = getSession(req);
+ String baseUrl = getAuthServerBaseUrl(req);
+ String realm = session.getRealm();
+ return KeycloakUriBuilder.fromUri(baseUrl).path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH)
+ .queryParam("referrer", "app-profile-jee").build(realm).toString();
+
+ }
+
+ private String getAuthServerBaseUrl(HttpServletRequest req) {
+ AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) req.getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(null);
+ return deployment.getAuthServerBaseUrl();
+ }
+
+ public String getTokenString(HttpServletRequest req) throws IOException {
+ return JsonSerialization.writeValueAsPrettyString(getIDToken(req));
+ }
+
+ private KeycloakSecurityContext getSession(HttpServletRequest req) {
+ return (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
+ }
+}
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp
new file mode 100644
index 0000000..beab972
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/index.jsp
@@ -0,0 +1,47 @@
+<%--
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.
+--%>
+
+<%@page contentType="text/html" pageEncoding="ISO-8859-1"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+ <title>Keycloak Example App</title>
+
+ <link rel="stylesheet" type="text/css" href="styles.css"/>
+ </head>
+ <body>
+ <jsp:useBean id="controller" class="org.keycloak.quickstart.profilejee.Controller" scope="request"/>
+ <% controller.handleLogout(request); %>
+
+ <c:set var="isLoggedIn" value="<%=controller.isLoggedIn(request)%>"/>
+ <c:if test="${isLoggedIn}">
+ <c:redirect url="profile.jsp"/>
+ </c:if>
+
+ <div class="wrapper" id="welcome">
+ <div class="menu">
+ <button onclick="location.href = 'profile.jsp'" type="button">Login</button>
+ </div>
+
+ <div class="content">
+ <div class="message">Please login</div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp
new file mode 100644
index 0000000..0b74a94
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/profile.jsp
@@ -0,0 +1,79 @@
+<%--
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.
+--%>
+
+<%@page contentType="text/html" pageEncoding="ISO-8859-1"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
+ <title>Keycloak Example App</title>
+ <link rel="stylesheet" type="text/css" href="styles.css"/>
+ </head>
+ <body>
+ <jsp:useBean id="controller" class="org.keycloak.quickstart.profilejee.Controller" scope="request"/>
+ <c:set var="idToken" value="<%=controller.getIDToken(request)%>"/>
+ <c:set var="tokenString" value="<%=controller.getTokenString(request)%>"/>
+ <c:set var="accountUri" value="<%=controller.getAccountUri(request)%>"/>
+ <c:set var="showToken" value="<%=controller.showToken(request)%>"/>
+
+ <div class="wrapper" id="profile">
+ <div class="menu">
+ <c:if test="${!showToken}">
+ <button onclick="location.href = 'profile.jsp?showToken=true'">Token</button>
+ </c:if>
+ <c:if test="${showToken}">
+ <button onclick="location.href = 'profile.jsp'">Profile</button>
+ </c:if>
+ <button onclick="location.href = 'index.jsp?logout=true'" type="button">Logout</button>
+ <button onclick="location.href = '${accountUri}'" type="button">Account</button>
+ </div>
+
+ <c:if test="${showToken}">
+ <div class="content">
+ <div id="token-content" class="message">${tokenString}</div>
+ <!-- <script>document.write(JSON.stringify(JSON.parse('${tokenString}'), null, ' '));</script>-->
+ </div>
+ </c:if>
+
+ <c:if test="${!showToken}">
+ <div class="content">
+ <div id="profile-content" class="message">
+ <table cellpadding="0" cellspacing="0">
+ <tr>
+ <td class="label">First name</td>
+ <td><span id="firstName">${idToken.givenName}</span></td>
+ </tr>
+ <tr class="even">
+ <td class="label">Last name</td>
+ <td><span id="lastName">${idToken.familyName}</span></td>
+ </tr>
+ <tr>
+ <td class="label">Username</td>
+ <td><span id="username">${idToken.preferredUsername}</span></td>
+ </tr>
+ <tr class="even">
+ <td class="label">Email</td>
+ <td><span id="email">${idToken.email}</span></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </c:if>
+ </div>
+ </body>
+</html>
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css
new file mode 100644
index 0000000..815abe4
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/styles.css
@@ -0,0 +1,101 @@
+
+body {
+ background-color: #333;
+ font-family: sans-serif;
+ font-size: 30px;
+}
+
+button {
+ font-family: sans-serif;
+ font-size: 30px;
+ width: 200px;
+
+ background-color: #0085cf;
+ background-image: linear-gradient(to bottom, #00a8e1 0%, #0085cf 100%);
+ background-repeat: repeat-x;
+
+ border: 2px solid #ccc;
+ color: #fff;
+ -webkit-border-radius: 30px;
+
+ text-transform: uppercase;
+
+ -webkit-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+ -moz-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+ box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+}
+
+button:hover {
+ background-color: #006ba6;
+ background-image: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+hr {
+ border: none;
+ background-color: #eee;
+ height: 10px;
+}
+
+.menu {
+ padding: 10px;
+ margin-bottom: 10px;
+}
+
+.content {
+ background-color: #eee;
+ border: 1px solid #ccc;
+ padding: 10px;
+ -webkit-border-radius: 10px;
+
+ -webkit-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+ -moz-box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+ box-shadow: 2px 2px 10px 0px rgba(0,0,0,0.5);
+}
+
+.content .message {
+ padding: 10px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ font-size: 40px;
+ -webkit-border-radius: 10px;
+}
+
+#token .content .message {
+ font-size: 20px;
+ overflow: scroll;
+ padding: 5px;
+ white-space: pre;
+ text-transform: none;
+}
+
+.wrapper {
+ position: absolute;
+ left: 10px;
+ top: 10px;
+ bottom: 10px;
+ right: 10px;
+}
+
+.error {
+ color: #a21e22;
+}
+
+table {
+ width: 100%;
+}
+
+tr.even {
+ background-color: #eee;
+}
+
+td {
+ padding: 5px;
+}
+
+td.label {
+ font-weight: bold;
+ width: 250px;
+}
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json
new file mode 100644
index 0000000..311f66d
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/keycloak.json
@@ -0,0 +1,10 @@
+{
+ "realm": "Test",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "/auth",
+ "ssl-required": "external",
+ "resource": "app-profile-jee",
+ "credentials": {
+ "secret": "4f36f31a-be9d-4f92-b982-425301bac5df"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c044282
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.
+-->
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>app-profile-jee</module-name>
+
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/profile.jsp</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ </login-config>
+
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+
+</web-app>
diff --git a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
index 1c2b61d..65540f1 100755
--- a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
+++ b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
@@ -43,6 +43,7 @@
<button onclick="output(keycloak.createRegisterUrl())">Show Register URL</button>
<button onclick="createBearerRequest()">Create Bearer Request</button>
<button onclick="output(showTime())">Show current time</button>
+ <button onclick="cert()">Cert request</button>
<input id="timeSkewInput"/>
<button onclick="addToTimeSkew()">timeSkew offset</button>
<button onclick="refreshTimeSkew()">refresh timeSkew</button>
@@ -215,6 +216,30 @@ TimeSkew: <div id="timeSkew"></div>
req.send();
}
+ function cert() {
+ var url = 'http://localhost:8180/auth/realms/example/protocol/openid-connect/certs';
+ if (window.location.href.indexOf("8543") > -1) {
+ url = url.replace("8180","8543");
+ url = url.replace("http","https");
+ }
+ var req = new XMLHttpRequest();
+ req.open('GET', url, true);
+ req.setRequestHeader('Accept', 'application/json');
+ req.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ output('Success');
+ } else if (req.status == 403) {
+ output('Forbidden');
+ } else if (req.status == 401) {
+ output('Unauthorized');
+ }
+ }
+ };
+ req.send();
+ }
+
var keycloak;
function keycloakInit() {
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 2acbdb0..4d74389 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -22,6 +22,7 @@
<module>hello-world-authz-service</module>
<module>servlet-authz</module>
<module>servlets</module>
+ <module>app-profile-jee</module>
</modules>
<build>
@@ -35,4 +36,4 @@
</plugin>
</plugins>
</build>
-</project>
\ No newline at end of file
+</project>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
index 4822c4b..069481f 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
@@ -77,6 +77,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
private WebElement createBearerRequest;
@FindBy(xpath = "//button[text() = 'Bearer to keycloak']")
private WebElement createBearerRequestToKeycloakButton;
+ @FindBy(xpath = "//button[text() = 'Cert request']")
+ private WebElement certRequestButton;
@FindBy(xpath = "//button[text() = 'refresh timeSkew']")
private WebElement refreshTimeSkewButton;
@@ -178,4 +180,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
public void refreshTimeSkew() {
refreshTimeSkewButton.click();
}
+
+ public void sendCertRequest() {
+ certRequestButton.click();
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java
new file mode 100644
index 0000000..f46c736
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/ProductPortalSubsystem.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 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.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+import java.net.URL;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class ProductPortalSubsystem extends AbstractPageWithInjectedUrl {
+
+ public static final String DEPLOYMENT_NAME = "product-portal-subsystem";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ return url;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
index 9c4f324..f8d8e98 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
@@ -37,6 +37,11 @@ public interface TestOIDCEndpointsApplicationResource {
@Path("/generate-keys")
Map<String, String> generateKeys();
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/get-keys-as-pem")
+ Map<String, String> getKeysAsPem();
+
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
index ea4725a..216e2ca 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java
@@ -74,19 +74,21 @@ public final class WaitUtils {
}
public static void pause(long millis) {
- log.info("Wait: " + millis + "ms");
- try {
- Thread.sleep(millis);
- } catch (InterruptedException ex) {
- Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
- Thread.currentThread().interrupt();
+ if (millis > 0) {
+ log.info("Wait: " + millis + "ms");
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException ex) {
+ Logger.getLogger(WaitUtils.class.getName()).log(Level.SEVERE, null, ex);
+ Thread.currentThread().interrupt();
+ }
}
}
/**
* Waits for page to finish any pending redirects, REST API requests etc.
- * Because Keycloak's Admin Console is a single-page application, we need to take extra steps to ensure
- * the page is fully loaded
+ * Because Keycloak's Admin Console is a single-page application, we need to
+ * take extra steps to ensure the page is fully loaded
*
* @param driver
*/
@@ -99,12 +101,11 @@ public final class WaitUtils {
// Checks if the document is ready and asks AngularJS, if present, whether there are any REST API requests
// in progress
wait.until(javaScriptThrowsNoExceptions(
- "if (document.readyState !== 'complete' " +
- "|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {" +
- "throw \"Not ready\";" +
- "}"));
- }
- catch (TimeoutException e) {
+ "if (document.readyState !== 'complete' "
+ + "|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {"
+ + "throw \"Not ready\";"
+ + "}"));
+ } catch (TimeoutException e) {
// Sometimes, for no obvious reason, the browser/JS doesn't set document.readyState to 'complete' correctly
// but that's no reason to let the test fail; after the timeout the page is surely fully loaded
log.warn("waitForPageToLoad time exceeded!");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
index e36bc36..179bb0f 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
@@ -190,6 +190,15 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
}
@Test
+ public void testCertEndpoint() {
+ logInAndInit("standard");
+ waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
+
+ jsConsoleTestAppPage.sendCertRequest();
+ waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
+ }
+
+ @Test
public void grantBrowserBasedApp() {
testRealmPage.setAuthRealm(EXAMPLE);
testRealmLoginPage.setAuthRealm(EXAMPLE);
@@ -323,6 +332,16 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
}
@Test
+ public void implicitFlowCertEndpoint() {
+ setImplicitFlowForClient();
+ logInAndInit("implicit");
+ waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
+
+ jsConsoleTestAppPage.sendCertRequest();
+ waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
+ }
+
+ @Test
public void testBearerRequest() {
jsConsoleTestAppPage.navigateTo();
jsConsoleTestAppPage.init();
@@ -406,6 +425,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
jsConsoleTestAppPage.setFlow(flow);
jsConsoleTestAppPage.init();
jsConsoleTestAppPage.logIn();
+ waitUntilElement(By.xpath("//body")).is().present();
testRealmLoginPage.form().login(user, "password");
jsConsoleTestAppPage.setFlow(flow);
jsConsoleTestAppPage.init();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
index fed5ab7..0ad81d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java
@@ -16,13 +16,6 @@ public abstract class AbstractDemoFilterServletAdapterTest extends AbstractDemoS
@Test
@Override
@Ignore
- public void testCustomerPortalWithSubsystemSettings() {
-
- }
-
- @Test
- @Override
- @Ignore
public void testAuthenticated() {
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index ed2191d..a7e5992 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.keycloak.testsuite.adapter.servlet;
import org.apache.commons.io.FileUtils;
@@ -32,7 +31,6 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.keys.KeyProvider;
-import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -48,7 +46,6 @@ import org.keycloak.testsuite.adapter.page.BasicAuth;
import org.keycloak.testsuite.adapter.page.CustomerDb;
import org.keycloak.testsuite.adapter.page.CustomerDbErrorPage;
import org.keycloak.testsuite.adapter.page.CustomerPortal;
-import org.keycloak.testsuite.adapter.page.CustomerPortalSubsystem;
import org.keycloak.testsuite.adapter.page.InputPortal;
import org.keycloak.testsuite.adapter.page.ProductPortal;
import org.keycloak.testsuite.adapter.page.SecurePortal;
@@ -58,7 +55,6 @@ import org.keycloak.testsuite.auth.page.account.Applications;
import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.console.page.events.Config;
import org.keycloak.testsuite.console.page.events.LoginEvents;
-import org.keycloak.testsuite.util.URLAssert;
import org.keycloak.testsuite.util.URLUtils;
import org.keycloak.util.BasicAuthHelper;
import org.openqa.selenium.By;
@@ -86,7 +82,6 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.keycloak.testsuite.adapter.page.CustomerPortalNoConf;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
@@ -107,8 +102,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
@Page
private CustomerPortalNoConf customerPortalNoConf;
@Page
- private CustomerPortalSubsystem customerPortalSubsystem;
- @Page
private SecurePortal securePortal;
@Page
private CustomerDb customerDb;
@@ -135,17 +128,12 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
protected static WebArchive customerPortal() {
return servletDeployment(CustomerPortal.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
}
-
+
@Deployment(name = CustomerPortalNoConf.DEPLOYMENT_NAME)
protected static WebArchive customerPortalNoConf() {
return servletDeployment(CustomerPortalNoConf.DEPLOYMENT_NAME, CustomerServletNoConf.class, ErrorServlet.class);
}
- @Deployment(name = CustomerPortalSubsystem.DEPLOYMENT_NAME)
- protected static WebArchive customerPortalSubsystem() {
- return servletDeployment(CustomerPortalSubsystem.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
- }
-
@Deployment(name = SecurePortal.DEPLOYMENT_NAME)
protected static WebArchive securePortal() {
return servletDeployment(SecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
@@ -198,14 +186,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
}
@Test
- public void testCustomerPortalWithSubsystemSettings() {
- customerPortalSubsystem.navigateTo();
- assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
- testRealmLoginPage.form().login("bburke@redhat.com", "password");
- assertTrue(driver.getPageSource().contains("Bill Burke") && driver.getPageSource().contains("Stian Thorgersen"));
- }
-
- @Test
public void testSavedPostRequest() throws InterruptedException {
// test login to customer-portal which does a bearer request to customer-db
inputPortal.navigateTo();
@@ -843,7 +823,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported.");
}
}
-
+
@Test
public void testWithoutKeycloakConf() {
customerPortalNoConf.navigateTo();
@@ -851,5 +831,4 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
assertTrue(pageSource.contains("Forbidden") || pageSource.contains("HTTP Status 401"));
}
-
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java
new file mode 100644
index 0000000..ad94cd1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractJBossOIDCServletsAdapterTest.java
@@ -0,0 +1,48 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.keycloak.testsuite.adapter.page.CustomerPortalSubsystem;
+import org.keycloak.testsuite.adapter.page.ProductPortalSubsystem;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
+
+/**
+ * OIDC adapter test specific for JBoss-based containers.
+ * @author tkyjovsk
+ */
+public abstract class AbstractJBossOIDCServletsAdapterTest extends AbstractDemoServletsAdapterTest {
+
+ @Page
+ private CustomerPortalSubsystem customerPortalSubsystem;
+
+ @Page
+ private ProductPortalSubsystem productPortalSubsystem;
+
+ @Deployment(name = CustomerPortalSubsystem.DEPLOYMENT_NAME)
+ protected static WebArchive customerPortalSubsystem() {
+ return servletDeployment(CustomerPortalSubsystem.DEPLOYMENT_NAME, CustomerServlet.class, ErrorServlet.class);
+ }
+
+ @Deployment(name = ProductPortalSubsystem.DEPLOYMENT_NAME)
+ protected static WebArchive productPortalSubsystem() {
+ return servletDeployment(ProductPortalSubsystem.DEPLOYMENT_NAME, ProductServlet.class);
+ }
+
+ @Test
+ public void testSecureDeployments() {
+ customerPortalSubsystem.navigateTo();
+ assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+ testRealmLoginPage.form().login("bburke@redhat.com", "password");
+ assertTrue(driver.getPageSource().contains("Bill Burke") && driver.getPageSource().contains("Stian Thorgersen"));
+
+ productPortalSubsystem.navigateTo();
+ assertCurrentUrlEquals(productPortalSubsystem);
+ String pageSource = driver.getPageSource();
+ assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
index 5adfb94..f2ba5ae 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
@@ -29,6 +29,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesPostSigPersistentServletPage.checkRoles(true);
salesPostSigTransientServletPage.checkRoles(true);
salesPostAssertionAndResponseSigPage.checkRoles(true);
+ employeeSigPostNoIdpKeyServletPage.checkRoles(true);
+ employeeSigRedirNoIdpKeyServletPage.checkRoles(true);
+ employeeSigRedirOptNoIdpKeyServletPage.checkRoles(true);
//using endpoint instead of query param because we are not able to put query param to IDP initiated login
employee2ServletPage.navigateTo();
@@ -54,6 +57,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesPostSigEmailServletPage.checkRoles(false);
salesPostSigPersistentServletPage.checkRoles(false);
salesPostSigTransientServletPage.checkRoles(false);
+ employeeSigPostNoIdpKeyServletPage.checkRoles(false);
+ employeeSigRedirNoIdpKeyServletPage.checkRoles(false);
+ employeeSigRedirOptNoIdpKeyServletPage.checkRoles(false);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
index 5e6b30d..9bdc40e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOIDCBrokerWithSignatureTest.java
@@ -28,6 +28,8 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.keys.loader.PublicKeyStorageManager;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
@@ -107,15 +109,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
@Test
public void testSignatureVerificationJwksUrl() throws Exception {
// Configure OIDC identity provider with JWKS URL
- IdentityProviderRepresentation idpRep = getIdentityProvider();
- OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
- cfg.setValidateSignature(true);
- cfg.setUseJwksUrl(true);
-
- UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
- String jwksUrl = b.build(bc.providerRealmName()).toString();
- cfg.setJwksUrl(jwksUrl);
- updateIdentityProvider(idpRep);
+ updateIdentityProviderWithJwksUrl();
// Check that user is able to login
logInAsUserInIDPForFirstTime();
@@ -139,6 +133,19 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
assertLoggedInAccountManagement();
}
+ // Configure OIDC identity provider with JWKS URL and validateSignature=true
+ private void updateIdentityProviderWithJwksUrl() {
+ IdentityProviderRepresentation idpRep = getIdentityProvider();
+ OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
+ cfg.setValidateSignature(true);
+ cfg.setUseJwksUrl(true);
+
+ UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
+ String jwksUrl = b.build(bc.providerRealmName()).toString();
+ cfg.setJwksUrl(jwksUrl);
+ updateIdentityProvider(idpRep);
+ }
+
@Test
public void testSignatureVerificationHardcodedPublicKey() throws Exception {
@@ -178,23 +185,17 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
@Test
public void testClearKeysCache() throws Exception {
// Configure OIDC identity provider with JWKS URL
- IdentityProviderRepresentation idpRep = getIdentityProvider();
- OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
- cfg.setValidateSignature(true);
- cfg.setUseJwksUrl(true);
-
- UriBuilder b = OIDCLoginProtocolService.certsUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT));
- String jwksUrl = b.build(bc.providerRealmName()).toString();
- cfg.setJwksUrl(jwksUrl);
- updateIdentityProvider(idpRep);
+ updateIdentityProviderWithJwksUrl();
// Check that user is able to login
logInAsUserInIDPForFirstTime();
assertLoggedInAccountManagement();
+ logoutFromRealm(bc.consumerRealmName());
// Check that key is cached
- String expectedCacheKey = consumerRealm().toRepresentation().getId() + "::idp::" + idpRep.getInternalId();
+ IdentityProviderRepresentation idpRep = getIdentityProvider();
+ String expectedCacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(consumerRealm().toRepresentation().getId(), idpRep.getInternalId());
TestingCacheResource cache = testingClient.testing(bc.consumerRealmName()).cache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
Assert.assertTrue(cache.contains(expectedCacheKey));
@@ -205,6 +206,40 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
}
+ // Test that when I update identityProvier, then the record in publicKey cache is cleared and it's not possible to authenticate with it anymore
+ @Test
+ public void testPublicKeyCacheInvalidatedWhenProviderUpdated() throws Exception {
+ // Configure OIDC identity provider with JWKS URL
+ updateIdentityProviderWithJwksUrl();
+
+ // Check that user is able to login
+ logInAsUserInIDPForFirstTime();
+ assertLoggedInAccountManagement();
+
+ logoutFromRealm(bc.consumerRealmName());
+
+ // Check that key is cached
+ IdentityProviderRepresentation idpRep = getIdentityProvider();
+ String expectedCacheKey = PublicKeyStorageUtils.getIdpModelCacheKey(consumerRealm().toRepresentation().getId(), idpRep.getInternalId());
+ TestingCacheResource cache = testingClient.testing(bc.consumerRealmName()).cache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
+ Assert.assertTrue(cache.contains(expectedCacheKey));
+
+ // Update identityProvider to some bad JWKS_URL
+ OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep);
+ cfg.setJwksUrl("http://localhost:43214/non-existent");
+ updateIdentityProvider(idpRep);
+
+ // Check that key is not cached anymore
+ Assert.assertFalse(cache.contains(expectedCacheKey));
+
+ // Check that user is not able to login with IDP
+ setTimeOffset(20);
+ logInAsUserInIDP();
+ assertErrorPage("Unexpected error when authenticating with identity provider");
+ }
+
+
+
private void rotateKeys() {
String activeKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java
new file mode 100644
index 0000000..4bb367f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java
@@ -0,0 +1,132 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.keycloak.testsuite.broker;
+
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
+import org.keycloak.testsuite.adapter.page.SalesPostServlet;
+import org.keycloak.testsuite.adapter.servlet.SendUsernameServlet;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.UpdateAccountInformationPage;
+import org.keycloak.testsuite.util.IOUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.support.ui.ExpectedCondition;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
+import static org.hamcrest.Matchers.*;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KcSamlIdPInitiatedSsoTest extends AbstractKeycloakTest {
+
+ private static final String PROVIDER_REALM_USER_NAME = "test";
+ private static final String PROVIDER_REALM_USER_PASSWORD = "test";
+
+ @Page
+ protected LoginPage accountLoginPage;
+
+ @Page
+ protected UpdateAccountInformationPage updateAccountInformationPage;
+
+ protected String getAuthRoot() {
+ return suiteContext.getAuthServerInfo().getContextRoot().toString();
+ }
+
+ private RealmRepresentation loadFromClasspath(String fileName, Properties properties) {
+ InputStream is = KcSamlIdPInitiatedSsoTest.class.getResourceAsStream(fileName);
+ try {
+ String template = StreamUtil.readString(is);
+ String realmString = StringPropertyReplacer.replaceProperties(template, properties);
+ return IOUtil.loadRealm(new ByteArrayInputStream(realmString.getBytes("UTF-8")));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ Properties p = new Properties();
+ p.put("name.realm.provider", REALM_PROV_NAME);
+ p.put("name.realm.consumer", REALM_CONS_NAME);
+ p.put("url.realm.provider", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME);
+ p.put("url.realm.consumer", getAuthRoot() + "/auth/realms/" + REALM_CONS_NAME);
+
+ testRealms.add(loadFromClasspath("kc3731-provider-realm.json", p));
+ testRealms.add(loadFromClasspath("kc3731-broker-realm.json", p));
+ }
+
+ @Test
+ public void testProviderIdpInitiatedLogin() {
+ driver.navigate().to(getSamlIdpInitiatedUrl(REALM_PROV_NAME, "samlbroker"));
+
+ waitForPage("log in to");
+
+ Assert.assertThat("Driver should be on the provider realm page right now",
+ driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_PROV_NAME + "/"));
+
+ log.debug("Logging in");
+ accountLoginPage.login(PROVIDER_REALM_USER_NAME, PROVIDER_REALM_USER_PASSWORD);
+
+ waitForPage("update account information");
+
+ Assert.assertTrue(updateAccountInformationPage.isCurrent());
+ Assert.assertThat("We must be on consumer realm right now",
+ driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_CONS_NAME + "/"));
+
+ log.debug("Updating info on updateAccount page");
+ updateAccountInformationPage.updateAccountInformation("mytest", "test@localhost", "Firstname", "Lastname");
+
+ UsersResource consumerUsers = adminClient.realm(REALM_CONS_NAME).users();
+
+ int userCount = consumerUsers.count();
+ Assert.assertTrue("There must be at least one user", userCount > 0);
+
+ List<UserRepresentation> users = consumerUsers.search("", 0, userCount);
+
+ boolean isUserFound = users.stream().anyMatch(user -> user.getUsername().equals("mytest") && user.getEmail().equals("test@localhost"));
+ Assert.assertTrue("There must be user " + "mytest" + " in realm " + REALM_CONS_NAME, isUserFound);
+
+ Assert.assertThat(driver.findElement(org.openqa.selenium.By.tagName("form")).getAttribute("action"), containsString("http://localhost:18080/sales-post-enc/"));
+ }
+
+ private String getSamlIdpInitiatedUrl(String realmName, String samlIdpInitiatedSsoUrlName) {
+ return getAuthRoot() + "/auth/realms/" + realmName + "/protocol/saml/clients/" + samlIdpInitiatedSsoUrlName;
+ }
+
+ private void waitForPage(final String title) {
+ WebDriverWait wait = new WebDriverWait(driver, 5);
+
+ ExpectedCondition<Boolean> condition = (WebDriver input) -> input.getTitle().toLowerCase().contains(title);
+
+ wait.until(condition);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
index c72bdba..f2f8c6d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java
@@ -57,6 +57,7 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation rep = new RealmRepresentation();
rep.setEnabled(true);
+ rep.setId(REALM_NAME);
rep.setRealm(REALM_NAME);
rep.setUsers(new LinkedList<UserRepresentation>());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
index 5a86d59..74432a8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCJwksClientRegistrationTest.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.client;
import java.security.KeyPair;
+import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
@@ -40,9 +41,14 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.client.registration.Auth;
import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.keys.PublicKeyStorageUtils;
+import org.keycloak.keys.loader.PublicKeyStorageManager;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@@ -139,6 +145,16 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
// The "kid" is set manually to some custom value
@Test
public void createClientWithJWKS_customKid() throws Exception {
+ OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+ Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
+
+ // Tries to authenticate client with privateKey JWT
+ assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+ }
+
+
+ private OIDCClientRepresentation createClientWithManuallySetKid(String kid) throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
@@ -146,20 +162,84 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
// Generate keys for client
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+ oidcClientEndpointsResource.generateKeys();
JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
// Override kid with custom value
- keySet.getKeys()[0].setKeyId("a1");
+ keySet.getKeys()[0].setKeyId(kid);
clientRep.setJwks(keySet);
- OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ return reg.oidc().create(clientRep);
+ }
+
+
+ @Test
+ public void testTwoClientsWithSameKid() throws Exception {
+ // Create client with manually set "kid"
+ OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+
+ // Create client2
+ OIDCClientRepresentation clientRep2 = createRep();
+
+ clientRep2.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
+ clientRep2.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
+
+ // Generate some random keys for client2
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ PublicKey client2PublicKey = generator.generateKeyPair().getPublic();
+
+ // Set client2 with manually set "kid" to be same like kid of client1 (but keys for both clients are different)
+ JSONWebKeySet keySet = new JSONWebKeySet();
+ keySet.setKeys(new JWK[]{JWKBuilder.create().kid("a1").rs256(client2PublicKey)});
+
+ clientRep2.setJwks(keySet);
+ clientRep2 = reg.oidc().create(clientRep2);
+
+
+ // Authenticate client1
+ Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
+ assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+
+ // Assert item in publicKey cache for client1
+ String expectedCacheKey = PublicKeyStorageUtils.getClientModelCacheKey(REALM_NAME, response.getClientId());
+ Assert.assertTrue(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+ // Assert it's not possible to authenticate as client2 with the same "kid" like client1
+ assertAuthenticateClientError(generatedKeys, clientRep2, "a1");
+ }
+
+
+ @Test
+ public void testPublicKeyCacheInvalidatedWhenUpdatingClient() throws Exception {
+ OIDCClientRepresentation response = createClientWithManuallySetKid("a1");
+
+ Map<String, String> generatedKeys = testingClient.testApp().oidcClientEndpoints().getKeysAsPem();
// Tries to authenticate client with privateKey JWT
assertAuthenticateClientSuccess(generatedKeys, response, "a1");
+
+ // Assert item in publicKey cache for client1
+ String expectedCacheKey = PublicKeyStorageUtils.getClientModelCacheKey(REALM_NAME, response.getClientId());
+ Assert.assertTrue(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+
+
+ // Update client with some bad JWKS_URI
+ response.setJwksUri("http://localhost:4321/non-existent");
+ reg.auth(Auth.token(response.getRegistrationAccessToken()))
+ .oidc().update(response);
+
+ // Assert item not any longer for client1
+ Assert.assertFalse(testingClient.testing().cache(InfinispanConnectionProvider.KEYS_CACHE_NAME).contains(expectedCacheKey));
+
+ // Assert it's not possible to authenticate as client1
+ assertAuthenticateClientError(generatedKeys, response, "a1");
}
+
@Test
public void createClientWithJWKSURI() throws Exception {
OIDCClientRepresentation clientRep = createRep();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index 0272584..d65fa94 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -176,6 +176,16 @@
"secret": "password"
},
{
+ "clientId": "product-portal-subsystem",
+ "enabled": true,
+ "adminUrl": "/product-portal-subsystem",
+ "baseUrl": "/product-portal-subsystem",
+ "redirectUris": [
+ "/product-portal-subsystem/*"
+ ],
+ "secret": "password"
+ },
+ {
"clientId": "secure-portal",
"enabled": true,
"adminUrl": "/secure-portal",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml
new file mode 100644
index 0000000..b4ddcce
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/META-INF/context.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright 2016 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.
+ -->
+
+<Context path="/customer-portal">
+ <Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
+</Context>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..8c59313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/jetty-web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+ ~ Copyright 2016 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.
+ -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+ <Get name="securityHandler">
+ <Set name="authenticator">
+ <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
+ <!--
+ <Set name="adapterConfig">
+ <New class="org.keycloak.representations.adapters.config.AdapterConfig">
+ <Set name="realm">tomcat</Set>
+ <Set name="resource">customer-portal</Set>
+ <Set name="authServerUrl">http://localhost:8180/auth</Set>
+ <Set name="sslRequired">external</Set>
+ <Set name="credentials">
+ <Map>
+ <Entry>
+ <Item>secret</Item>
+ <Item>password</Item>
+ </Entry>
+ </Map>
+ </Set>
+ <Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
+ </New>
+ </Set>
+ -->
+ </New>
+ </Set>
+ </Get>
+</Configure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml
new file mode 100644
index 0000000..0af7958
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/product-portal-subsystem/WEB-INF/web.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016 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.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>product-portal-subsystem</module-name>
+
+ <servlet>
+ <servlet-name>Servlet</servlet-name>
+ <servlet-class>org.keycloak.testsuite.adapter.servlet.ProductServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>Servlet</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Users</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>demo</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json
new file mode 100644
index 0000000..6e5c7e0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-broker-realm.json
@@ -0,0 +1,64 @@
+{
+ "id" : "${name.realm.consumer}",
+ "realm" : "${name.realm.consumer}",
+ "enabled" : true,
+ "sslRequired" : "external",
+ "roles" : {
+ "client" : {
+ "http://localhost:18080/sales-post-enc/" : [ {
+ "name" : "manager"
+ } ]
+ }
+ },
+ "clients" : [ {
+ "clientId": "http://localhost:18080/sales-post-enc/",
+ "enabled": true,
+ "protocol": "saml",
+ "fullScopeAllowed": true,
+ "redirectUris": [
+ "http://localhost:18080/sales-post-enc/*"
+ ],
+ "attributes": {
+ "saml.authnstatement": "true",
+ "saml.client.signature": "true",
+ "saml.encrypt": "false",
+ "saml.server.signature": "true",
+ "saml.signature.algorithm": "RSA_SHA512",
+ "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
+ "saml.signing.private.key": "MIICXQIBAAKBgQDb7kwJPkGdU34hicplwfp6/WmNcaLh94TSc7Jyr9Undp5pkyLgb0DE7EIE+6kSs4LsqCb8HDkB0nLD5DXbBJFd8n0WGoKstelvtg6FtVJMnwN7k7yZbfkPECWH9zF70VeOo9vbzrApNRnct8ZhH5fbflRB4JMA9L9R+LbURdoSKQIDAQABAoGBANtbZG9bruoSGp2s5zhzLzd4hczT6Jfk3o9hYjzNb5Z60ymN3Z1omXtQAdEiiNHkRdNxK+EM7TcKBfmoJqcaeTkW8cksVEAW23ip8W9/XsLqmbU2mRrJiKa+KQNDSHqJi1VGyimi4DDApcaqRZcaKDFXg2KDr/Qt5JFD/o9IIIPZAkEA+ZENdBIlpbUfkJh6Ln+bUTss/FZ1FsrcPZWu13rChRMrsmXsfzu9kZUWdUeQ2Dj5AoW2Q7L/cqdGXS7Mm5XhcwJBAOGZq9axJY5YhKrsksvYRLhQbStmGu5LG75suF+rc/44sFq+aQM7+oeRr4VY88Mvz7mk4esdfnk7ae+cCazqJvMCQQCx1L1cZw3yfRSn6S6u8XjQMjWE/WpjulujeoRiwPPY9WcesOgLZZtYIH8nRL6ehEJTnMnahbLmlPFbttxPRUanAkA11MtSIVcKzkhp2KV2ipZrPJWwI18NuVJXb+3WtjypTrGWFZVNNkSjkLnHIeCYlJIGhDd8OL9zAiBXEm6kmgLNAkBWAg0tK2hCjvzsaA505gWQb4X56uKWdb0IzN+fOLB3Qt7+fLqbVQNQoNGzqey6B4MoS1fUKAStqdGTFYPG/+9t",
+ "saml_idp_initiated_sso_url_name" : "sales"
+ },
+ "baseUrl": "http://localhost:18080/sales-post-enc/",
+ "adminUrl": "http://localhost:18080/sales-post-enc/saml"
+ } ],
+ "identityProviders" : [ {
+ "alias" : "saml-leaf",
+ "providerId" : "saml",
+ "enabled" : true,
+ "updateProfileFirstLoginMode" : "on",
+ "trustEmail" : false,
+ "storeToken" : false,
+ "addReadTokenRoleOnCreate" : false,
+ "authenticateByDefault" : false,
+ "firstBrokerLoginFlowAlias" : "first broker login",
+ "config" : {
+ "nameIDPolicyFormat" : "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
+ "postBindingAuthnRequest" : "true",
+ "postBindingResponse" : "true",
+ "singleLogoutServiceUrl" : "${url.realm.provider}/protocol/saml",
+ "singleSignOnServiceUrl" : "${url.realm.provider}/protocol/saml",
+ "validateSignature" : "false",
+ "wantAuthnRequestsSigned" : "false"
+ }
+ } ],
+ "identityProviderMappers" : [ {
+ "name" : "manager-role",
+ "identityProviderAlias" : "saml-leaf",
+ "identityProviderMapper" : "saml-role-idp-mapper",
+ "config" : {
+ "attribute.value" : "manager",
+ "role" : "http://localhost:18080/sales-post-enc/.manager",
+ "attribute.name" : "Role"
+ }
+ } ]
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json
new file mode 100644
index 0000000..8804a36
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/broker/kc3731-provider-realm.json
@@ -0,0 +1,49 @@
+{
+ "id" : "${name.realm.provider}",
+ "realm" : "${name.realm.provider}",
+ "enabled" : true,
+ "sslRequired" : "external",
+ "roles" : {
+ "client" : {
+ "${url.realm.consumer}" : [ {
+ "name" : "manager"
+ } ]
+ }
+ },
+ "clients" : [ {
+ "clientId": "${url.realm.consumer}",
+ "enabled": true,
+ "protocol": "saml",
+ "fullScopeAllowed": true,
+ "redirectUris": [
+ "${url.realm.consumer}/broker/saml-leaf/endpoint"
+ ],
+ "attributes" : {
+ "saml.assertion.signature" : "false",
+ "saml.authnstatement" : "true",
+ "saml.client.signature" : "false",
+ "saml.encrypt" : "false",
+ "saml.force.post.binding" : "true",
+ "saml.server.signature" : "false",
+ "saml_assertion_consumer_url_post" : "${url.realm.consumer}/broker/saml-leaf/endpoint/clients/sales",
+ "saml_force_name_id_format" : "false",
+ "saml_idp_initiated_sso_url_name" : "samlbroker",
+ "saml_name_id_format" : "persistent",
+ "saml_single_logout_service_url_post" : "${url.realm.consumer}/broker/saml-leaf/endpoint"
+ }
+ } ],
+ "users" : [ {
+ "username" : "test",
+ "enabled" : true,
+ "email" : "a@localhost",
+ "firstName": "b",
+ "lastName": "c",
+ "credentials" : [ {
+ "type" : "password",
+ "value" : "test"
+ } ],
+ "clientRoles" : {
+ "${url.realm.consumer}" : [ "manager" ]
+ }
+ } ]
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
index bd9843e..281de0b 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/as7/src/test/java/org/keycloak/testsuite/adapter/AS7OIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-as7")
-public class AS7OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class AS7OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
index 0027550..114d875 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem.xsl
@@ -21,6 +21,16 @@
<resource>customer-portal-subsystem</resource>
<credential name="secret">password</credential>
</secure-deployment>
+
+ <secure-deployment name="product-portal-subsystem.war">
+ <realm>demo</realm>
+ <realm-public-key>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</realm-public-key>
+ <auth-server-url><xsl:value-of select="$auth-server-host"/>/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <resource>product-portal-subsystem</resource>
+ <credential name="secret">password</credential>
+ </secure-deployment>
+
</xsl:copy>
</xsl:template>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl
new file mode 100644
index 0000000..3306012
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/common/xslt/keycloak-subsystem_separate-realm-def.xsl
@@ -0,0 +1,45 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ version="2.0"
+ exclude-result-prefixes="xalan">
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="keycloakSubsystem" select="'urn:jboss:domain:keycloak:1.1'"/>
+ <xsl:param name="auth-server-host"/>
+
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $keycloakSubsystem)]">
+ <xsl:copy>
+ <xsl:apply-templates select="@* | node()" />
+
+ <realm name="demo">
+ <realm-public-key>MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</realm-public-key>
+ <auth-server-url>
+ <xsl:value-of select="$auth-server-host"/>/auth
+ </auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ </realm>
+
+ <secure-deployment name="customer-portal-subsystem.war">
+ <realm>demo</realm>
+ <resource>customer-portal-subsystem</resource>
+ <credential name="secret">password</credential>
+ </secure-deployment>
+
+ <secure-deployment name="product-portal-subsystem.war">
+ <realm>demo</realm>
+ <resource>product-portal-subsystem</resource>
+ <credential name="secret">password</credential>
+ </secure-deployment>
+
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
index 169cc6e..b5f3ee9 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/EAPOIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-eap")
-public class EAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class EAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
index 4afd228..1367d95 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6OIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-eap6")
-public class EAP6OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class EAP6OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
index 72e4c20..d59e6b0 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6-fuse/pom.xml
@@ -29,7 +29,7 @@
<artifactId>integration-arquillian-tests-adapters-eap6-fuse</artifactId>
- <name>Adapter Tests - JBoss - EAP 6</name>
+ <name>Adapter Tests - JBoss - EAP 6 Fuse</name>
<properties>
<app.server>eap6-fuse</app.server>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
index defe63a..17a362f 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
@@ -36,6 +36,8 @@
<properties>
<common.resources>${project.parent.basedir}/common</common.resources>
<app.server.type>managed</app.server.type>
+ <auth.server.actual.http.port>${auth.server.http.port}</auth.server.actual.http.port>
+ <keycloak.subsystem.xsl>keycloak-subsystem.xsl</keycloak.subsystem.xsl>
</properties>
<build>
@@ -57,12 +59,12 @@
<includes>
<include>standalone.xml</include>
</includes>
- <stylesheet>${common.resources}/xslt/keycloak-subsystem.xsl</stylesheet>
+ <stylesheet>${common.resources}/xslt/${keycloak.subsystem.xsl}</stylesheet>
<outputDir>${app.server.home}/standalone/configuration</outputDir>
<parameters>
<parameter>
<name>auth-server-host</name>
- <value>http://localhost:${auth.server.http.port}</value>
+ <value>http://localhost:${auth.server.actual.http.port}</value>
</parameter>
</parameters>
</transformationSet>
@@ -83,41 +85,17 @@
<value>true</value>
</property>
</activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>xml-maven-plugin</artifactId>
- <executions>
- <execution>
- <id>configure-keycloak-subsystem</id>
- <phase>process-test-resources</phase>
- <goals>
- <goal>transform</goal>
- </goals>
- <configuration>
- <transformationSets>
- <transformationSet>
- <dir>${app.server.home}/standalone/configuration</dir>
- <includes>
- <include>standalone.xml</include>
- </includes>
- <stylesheet>${common.resources}/xslt/keycloak-subsystem.xsl</stylesheet>
- <outputDir>${app.server.home}/standalone/configuration</outputDir>
- <parameters>
- <parameter>
- <name>auth-server-host</name>
- <value>https://localhost:${auth.server.https.port}</value>
- </parameter>
- </parameters>
- </transformationSet>
- </transformationSets>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
+ <properties>
+ <!-- one realm definition for each secure-deployment -->
+ <auth.server.actual.http.port>${auth.server.https.port}</auth.server.actual.http.port>
+ </properties>
+ </profile>
+ <profile>
+ <id>keycloak-subsystem-separate-realm</id>
+ <properties>
+ <!-- single realm definition, multiple secure-deployments -->
+ <keycloak.subsystem.xsl>keycloak-subsystem_separate-realm-def.xsl</keycloak.subsystem.xsl>
+ </properties>
</profile>
<profile>
<id>adapter-test-jboss-submodules</id>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
index 8d0cedc..fe87f9f 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/RelativeEAPOIDCAdapterTest.java
@@ -1,11 +1,11 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
/**
*
* @author tkyjovsk
*/
-public class RelativeEAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class RelativeEAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
index 09ed2bc..0e14d93 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/RelativeWildflyOIDCAdapterTest.java
@@ -1,11 +1,11 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
/**
*
* @author tkyjovsk
*/
-public class RelativeWildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class RelativeWildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
index b07ae38..989dd2b 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
@@ -24,7 +24,7 @@
<parent>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-adapters-jboss</artifactId>
- <version>2.1.0-SNAPSHOT</version>
+ <version>2.4.1.Final-SNAPSHOT</version>
</parent>
<artifactId>integration-arquillian-tests-adapters-remote</artifactId>
@@ -70,9 +70,9 @@
<configuration>
<artifactItems>
<artifactItem>
- <groupId>org.keycloak.quickstart</groupId>
- <artifactId>keycloak-quickstart-app-profile-jee</artifactId>
- <version>0.5-SNAPSHOT</version>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>keycloak-test-app-profile-jee</artifactId>
+ <version>${project.version}</version>
<type>war</type>
</artifactItem>
</artifactItems>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
index a853e2b..c665f79 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/performance/httpclient/HttpClientLoginLogoutPerfTest.java
@@ -53,7 +53,7 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
private static final Logger LOG = Logger.getLogger(HttpClientLoginLogoutPerfTest.class);
- private static final String EXAMPLES = "Examples";
+ private static final String TEST_REALM = "Test";
private String securedUrl;
private String logoutUrl;
@@ -72,18 +72,18 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
@Deployment(name = AppProfileJEE.DEPLOYMENT_NAME)
private static WebArchive appProfileJEE() throws IOException {
- return warDeployment("keycloak-quickstart-app-profile-jee-0.5-SNAPSHOT");
+ return exampleDeployment("keycloak-test-app-profile-jee");
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
- testRealmPage.setAuthRealm(EXAMPLES);
+ testRealmPage.setAuthRealm(TEST_REALM);
}
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
- RealmRepresentation examplesRealm = loadRealm("/examples-realm.json");
+ RealmRepresentation examplesRealm = loadRealm("/test-realm.json");
examplesRealm.setPasswordPolicy("hashIterations(" + PASSWORD_HASH_ITERATIONS + ")");
testRealms.add(examplesRealm);
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json
new file mode 100644
index 0000000..2ae0417
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/test-realm.json
@@ -0,0 +1,52 @@
+{
+ "id": "Test",
+ "realm": "Test",
+ "enabled": true,
+ "accessTokenLifespan": 600,
+ "accessCodeLifespan": 10,
+ "accessCodeLifespanUserAction": 6000,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+ "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "requiredCredentials": ["password"],
+ "users": [{
+ "username": "admin-user",
+ "enabled": true,
+ "firstName": "Admin",
+ "lastName": "User",
+ "email": "admin@user.com",
+ "credentials": [{
+ "type": "password",
+ "value": "password"
+ }],
+ "realmRoles": ["offline_access", "user", "admin"],
+ "clientRoles": {
+ "account": ["view-profile", "manage-account"]
+ }
+ }, {
+ "username": "secure-user",
+ "enabled": true,
+ "firstName": "Secure",
+ "lastName": "User",
+ "email": "secure@user.com",
+ "credentials": [{
+ "type": "password",
+ "value": "password"
+ }],
+ "realmRoles": ["offline_access", "user"],
+ "clientRoles": {
+ "account": ["view-profile", "manage-account"]
+ }
+ }],
+ "clients": [
+ {
+ "clientId": "app-profile-jee",
+ "enabled": true,
+ "adminUrl": "/app-profile-jee/",
+ "baseUrl": "/app-profile-jee/",
+ "redirectUris": ["/app-profile-jee/*"],
+ "secret": "4f36f31a-be9d-4f92-b982-425301bac5df"
+ }
+ ]
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
index 51ba75e..abea3db 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-wildfly")
-public class WildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class WildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
index f853f0d..2ffdfb2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly8/src/test/java/org/keycloak/testsuite/adapter/Wildfly8OIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-wildfly8")
-public class Wildfly8OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class Wildfly8OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
index 8c92df2..0ffc804 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly9/src/test/java/org/keycloak/testsuite/adapter/Wildfly9OIDCAdapterTest.java
@@ -1,6 +1,6 @@
package org.keycloak.testsuite.adapter;
-import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
+import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
/**
@@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
* @author tkyjovsk
*/
@AppServerContainer("app-server-wildfly9")
-public class Wildfly9OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
+public class Wildfly9OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
}
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
index 501583f..fc26648 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/authentication/PasswordPolicy.java
@@ -6,6 +6,7 @@ import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
+import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
@@ -33,8 +34,9 @@ public class PasswordPolicy extends Authentication {
public void addPolicy(Type policy, String value) {
waitUntilElement(addPolicySelectElement).is().present();
addPolicySelect.selectByVisibleText(policy.getName());
- setPolicyValue(policy, value);
+ if (value != null) {setPolicyValue(policy, value);}
primaryButton.click();
+ waitForPageToLoad(driver);
}
@@ -43,15 +45,13 @@ public class PasswordPolicy extends Authentication {
}
public void addPolicy(Type policy) {
- addPolicySelect.selectByVisibleText(policy.getName());
- primaryButton.click();
+ addPolicy(policy, null);
}
public void removePolicy(Type policy) {
getPolicyRow(policy).findElement(By.cssSelector("td.kc-action-cell")).click();
- if (!primaryButton.isDisplayed()) {
- primaryButton.click();
- }
+ primaryButton.click();
+ waitForPageToLoad(driver);
}
public void editPolicy(Type policy, int value) {
@@ -61,6 +61,7 @@ public class PasswordPolicy extends Authentication {
public void editPolicy(Type policy, String value) {
setPolicyValue(policy, value);
primaryButton.click();
+ waitForPageToLoad(driver);
}
private void setPolicyValue(Type policy, String value) {
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
index f20348a..97a4b21 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/authentication/PasswordPolicyTest.java
@@ -49,6 +49,7 @@ public class PasswordPolicyTest extends AbstractConsoleTest {
public void testAddAndRemovePolicy() {
passwordPolicyPage.navigateTo();
passwordPolicyPage.addPolicy(DIGITS, 5);
+ assertAlertSuccess();
passwordPolicyPage.removePolicy(DIGITS);
assertAlertSuccess();
}
diff --git a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
index 48ba4b4..56fe43a 100644
--- a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
+++ b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
@@ -106,7 +106,6 @@ public class SSSDTest extends AbstractKeycloakTest {
driver.navigate().to(getAccountUrl());
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
-
Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction());
}
@@ -117,8 +116,7 @@ public class SSSDTest extends AbstractKeycloakTest {
driver.navigate().to(getAccountUrl());
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
accountLoginPage.login(USERNAME, PASSWORD);
- Assert.assertEquals("Browser should be on account page now, logged in", "Keycloak Account Management", driver.getTitle());
-
+ Assert.assertTrue(profilePage.isCurrent());
testUserGroups();
}
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 9668db4..bb9e838 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -209,7 +209,7 @@
</properties>
<dependencies>
<dependency>
- <groupId>org.wildfly</groupId>
+ <groupId>org.wildfly.arquillian</groupId>
<artifactId>wildfly-arquillian-container-remote</artifactId>
</dependency>
</dependencies>
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index ed747ae..81af97b 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -637,6 +637,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
console.log('UserFederationCtrl ++++****');
$scope.realm = realm;
$scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
+ $scope.instancesLoaded = false;
if (!$scope.providers) $scope.providers = [];
@@ -716,7 +717,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
data[i].isUserFederationProvider = true;
$scope.instances.push(data[i]);
}
-
+ $scope.instancesLoaded = true;
});
});
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
index e4d2e4f..8298a99 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-federation.html
@@ -3,7 +3,7 @@
<span>{{:: 'user-federation' | translate}}</span>
</h1>
- <div class="blank-slate-pf" data-ng-hide="instances && instances.length > 0">
+ <div class="blank-slate-pf" data-ng-hide="!instancesLoaded || (instances && instances.length > 0)">
<div class="blank-slate-pf-icon">
<span class="fa fa-database"></span>
</div>
diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
index 3240625..538ed81 100755
--- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
+++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java
@@ -85,7 +85,7 @@ public final class KeycloakAdapterConfigService {
// be where the Keycloak server's Config interface expects it to be.
private void massageScheduledTaskInterval(ModelNode copy) {
- if (!copy.hasDefined("scheduled-task-intervale")) return;
+ if (!copy.hasDefined("scheduled-task-interval")) return;
ModelNode taskInterval = copy.remove("scheduled-task-interval");
copy.get("scheduled", "interval").set(taskInterval);
}