Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
7efa3a3ddf
65 changed files with 1732 additions and 3793 deletions
|
@ -19,6 +19,7 @@ package org.keycloak.keys.infinispan;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
@ -32,6 +33,7 @@ import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.keys.PublicKeyLoader;
|
import org.keycloak.keys.PublicKeyLoader;
|
||||||
import org.keycloak.keys.PublicKeyStorageProvider;
|
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
|
import org.keycloak.models.cache.infinispan.ClearCacheEvent;
|
||||||
import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;
|
import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;
|
||||||
|
|
||||||
|
@ -51,13 +53,17 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
|
||||||
|
|
||||||
private final int minTimeBetweenRequests ;
|
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) {
|
public InfinispanPublicKeyStorageProvider(KeycloakSession session, Cache<String, PublicKeysEntry> keys, Map<String, FutureTask<PublicKeysEntry>> tasksInProgress, int minTimeBetweenRequests) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.tasksInProgress = tasksInProgress;
|
this.tasksInProgress = tasksInProgress;
|
||||||
this.minTimeBetweenRequests = minTimeBetweenRequests;
|
this.minTimeBetweenRequests = minTimeBetweenRequests;
|
||||||
|
session.getTransactionManager().enlistAfterCompletion(getAfterTransaction());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
keys.clear();
|
keys.clear();
|
||||||
|
@ -65,6 +71,56 @@ public class InfinispanPublicKeyStorageProvider implements PublicKeyStorageProvi
|
||||||
cluster.notify(InfinispanPublicKeyStorageProviderFactory.KEYS_CLEAR_CACHE_EVENTS, new ClearCacheEvent(), true);
|
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
|
@Override
|
||||||
public PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
|
public PublicKey getPublicKey(String modelKey, String kid, PublicKeyLoader loader) {
|
||||||
// Check if key is in cache
|
// Check if key is in cache
|
||||||
|
|
|
@ -30,9 +30,14 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.keys.PublicKeyStorageProvider;
|
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||||
import org.keycloak.keys.PublicKeyStorageSpi;
|
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||||
import org.keycloak.keys.PublicKeyStorageProviderFactory;
|
import org.keycloak.keys.PublicKeyStorageProviderFactory;
|
||||||
|
import org.keycloak.keys.PublicKeyStorageUtils;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
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>
|
* @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";
|
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<>();
|
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);
|
this.keysCache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
|
||||||
|
|
||||||
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
|
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) -> {
|
cluster.registerListener(KEYS_CLEAR_CACHE_EVENTS, (ClusterEvent event) -> {
|
||||||
|
|
||||||
keysCache.clear();
|
keysCache.clear();
|
||||||
|
@ -82,6 +96,55 @@ public class InfinispanPublicKeyStorageProviderFactory implements PublicKeyStora
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 + " ]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -960,6 +960,15 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
||||||
|
|
||||||
for (IdentityProviderEntity entity: entities) {
|
for (IdentityProviderEntity entity: entities) {
|
||||||
|
IdentityProviderModel identityProviderModel = entityToModel(entity);
|
||||||
|
|
||||||
|
identityProviders.add(identityProviderModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(identityProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
|
||||||
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
|
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
|
||||||
identityProviderModel.setProviderId(entity.getProviderId());
|
identityProviderModel.setProviderId(entity.getProviderId());
|
||||||
identityProviderModel.setAlias(entity.getAlias());
|
identityProviderModel.setAlias(entity.getAlias());
|
||||||
|
@ -977,11 +986,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
|
identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
|
||||||
identityProviderModel.setStoreToken(entity.isStoreToken());
|
identityProviderModel.setStoreToken(entity.isStoreToken());
|
||||||
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
|
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
|
||||||
|
return identityProviderModel;
|
||||||
identityProviders.add(identityProviderModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.unmodifiableList(identityProviders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1024,8 +1029,28 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
public void removeIdentityProviderByAlias(String alias) {
|
public void removeIdentityProviderByAlias(String alias) {
|
||||||
for (IdentityProviderEntity entity : realm.getIdentityProviders()) {
|
for (IdentityProviderEntity entity : realm.getIdentityProviders()) {
|
||||||
if (entity.getAlias().equals(alias)) {
|
if (entity.getAlias().equals(alias)) {
|
||||||
|
|
||||||
em.remove(entity);
|
em.remove(entity);
|
||||||
em.flush();
|
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();
|
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
|
@Override
|
||||||
|
|
|
@ -775,6 +775,15 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
||||||
|
|
||||||
for (IdentityProviderEntity entity: entities) {
|
for (IdentityProviderEntity entity: entities) {
|
||||||
|
IdentityProviderModel identityProviderModel = entityToModel(entity);
|
||||||
|
|
||||||
|
identityProviders.add(identityProviderModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(identityProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IdentityProviderModel entityToModel(IdentityProviderEntity entity) {
|
||||||
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
|
IdentityProviderModel identityProviderModel = new IdentityProviderModel();
|
||||||
|
|
||||||
identityProviderModel.setProviderId(entity.getProviderId());
|
identityProviderModel.setProviderId(entity.getProviderId());
|
||||||
|
@ -792,11 +801,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
|
identityProviderModel.setPostBrokerLoginFlowId(entity.getPostBrokerLoginFlowId());
|
||||||
identityProviderModel.setStoreToken(entity.isStoreToken());
|
identityProviderModel.setStoreToken(entity.isStoreToken());
|
||||||
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
|
identityProviderModel.setAddReadTokenRoleOnCreate(entity.isAddReadTokenRoleOnCreate());
|
||||||
|
return identityProviderModel;
|
||||||
identityProviders.add(identityProviderModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Collections.unmodifiableList(identityProviders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -837,6 +842,25 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
if (entity.getAlias().equals(alias)) {
|
if (entity.getAlias().equals(alias)) {
|
||||||
realm.getIdentityProviders().remove(entity);
|
realm.getIdentityProviders().remove(entity);
|
||||||
updateRealm();
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -860,6 +884,24 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRealm();
|
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
|
@Override
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -67,6 +67,18 @@ public interface RealmModel extends RoleContainerModel {
|
||||||
KeycloakSession getKeycloakSession();
|
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 getId();
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
|
|
|
@ -66,6 +66,7 @@ import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
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_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
|
||||||
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
|
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
|
||||||
public static final String SAML_ASSERTION = "SAML_ASSERTION";
|
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";
|
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
|
||||||
protected RealmModel realm;
|
protected RealmModel realm;
|
||||||
protected EventBuilder event;
|
protected EventBuilder event;
|
||||||
|
@ -130,7 +132,7 @@ public class SAMLEndpoint {
|
||||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
@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,
|
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
@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 {
|
protected abstract class Binding {
|
||||||
|
@ -194,12 +218,12 @@ public class SAMLEndpoint {
|
||||||
return new HardcodedKeyLocator(keys);
|
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);
|
event = new EventBuilder(realm, session, clientConnection);
|
||||||
Response response = basicChecks(samlRequest, samlResponse);
|
Response response = basicChecks(samlRequest, samlResponse);
|
||||||
if (response != null) return response;
|
if (response != null) return response;
|
||||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
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) {
|
protected Response handleSamlRequest(String samlRequest, String relayState) {
|
||||||
|
@ -304,7 +328,7 @@ public class SAMLEndpoint {
|
||||||
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
private String getEntityId(UriInfo uriInfo, RealmModel realm) {
|
||||||
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
|
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 {
|
try {
|
||||||
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
|
||||||
|
@ -316,6 +340,9 @@ public class SAMLEndpoint {
|
||||||
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
|
||||||
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
|
||||||
identity.getContextData().put(SAML_ASSERTION, assertion);
|
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());
|
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);
|
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||||
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
||||||
// validate destination
|
// validate destination
|
||||||
|
@ -390,7 +417,7 @@ public class SAMLEndpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (statusResponse instanceof ResponseType) {
|
if (statusResponse instanceof ResponseType) {
|
||||||
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
|
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState, clientId);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// todo need to check that it is actually a LogoutResponse
|
// todo need to check that it is actually a LogoutResponse
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.security.PublicKey;
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.keys.PublicKeyStorageProvider;
|
import org.keycloak.keys.PublicKeyStorageProvider;
|
||||||
|
import org.keycloak.keys.PublicKeyStorageUtils;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -36,27 +37,20 @@ public class PublicKeyStorageManager {
|
||||||
|
|
||||||
PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
|
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);
|
ClientPublicKeyLoader loader = new ClientPublicKeyLoader(session, client);
|
||||||
return keyStorage.getPublicKey(modelKey, kid, loader);
|
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) {
|
public static PublicKey getIdentityProviderPublicKey(KeycloakSession session, RealmModel realm, OIDCIdentityProviderConfig idpConfig, JWSInput input) {
|
||||||
String kid = input.getHeader().getKeyId();
|
String kid = input.getHeader().getKeyId();
|
||||||
|
|
||||||
PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class);
|
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);
|
OIDCIdentityProviderPublicKeyLoader loader = new OIDCIdentityProviderPublicKeyLoader(session, idpConfig);
|
||||||
return keyStorage.getPublicKey(modelKey, kid, loader);
|
return keyStorage.getPublicKey(modelKey, kid, loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getModelKey(RealmModel realm, OIDCIdentityProviderConfig idpConfig) {
|
|
||||||
return realm.getId() + "::idp::" + idpConfig.getInternalId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -611,12 +611,29 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
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;
|
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) {
|
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;
|
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
String redirect = null;
|
String redirect;
|
||||||
if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
|
if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
|
||||||
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -640,8 +657,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newBrowserAuthentication(clientSession, false, false);
|
return clientSession;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.services.resources;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
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.IdentityProvider;
|
||||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||||
|
import org.keycloak.broker.saml.SAMLEndpoint;
|
||||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.common.util.ObjectUtil;
|
import org.keycloak.common.util.ObjectUtil;
|
||||||
|
@ -54,8 +56,11 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
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.provider.ProviderFactory;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
@ -87,6 +92,8 @@ import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
import static org.keycloak.models.AccountRoles.MANAGE_ACCOUNT;
|
||||||
|
@ -255,7 +262,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
public Response authenticated(BrokeredIdentityContext context) {
|
public Response authenticated(BrokeredIdentityContext context) {
|
||||||
IdentityProviderModel identityProviderConfig = context.getIdpConfig();
|
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) {
|
if (parsedCode.response != null) {
|
||||||
return parsedCode.response;
|
return parsedCode.response;
|
||||||
}
|
}
|
||||||
|
@ -696,6 +708,53 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
|
||||||
return ParsedCodeContext.response(staleCodeError);
|
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) {
|
private Response checkAccountManagementFailedLinking(ClientSessionModel clientSession, String error, Object... parameters) {
|
||||||
if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
|
if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
@ -63,13 +65,20 @@ public class TestingOIDCEndpointsApplicationResource {
|
||||||
@NoCache
|
@NoCache
|
||||||
public Map<String, String> generateKeys() {
|
public Map<String, String> generateKeys() {
|
||||||
try {
|
try {
|
||||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048);
|
||||||
generator.initialize(2048);
|
clientData.setSigningKeyPair(keyPair);
|
||||||
clientData.setSigningKeyPair(generator.generateKeyPair());
|
} catch (Exception e) {
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new BadRequestException("Error generating signing keypair", 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 privateKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPrivate());
|
||||||
String publicKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPublic());
|
String publicKeyPem = PemUtils.encodeKey(clientData.getSigningKeyPair().getPublic());
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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>
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
|
@ -43,6 +43,7 @@
|
||||||
<button onclick="output(keycloak.createRegisterUrl())">Show Register URL</button>
|
<button onclick="output(keycloak.createRegisterUrl())">Show Register URL</button>
|
||||||
<button onclick="createBearerRequest()">Create Bearer Request</button>
|
<button onclick="createBearerRequest()">Create Bearer Request</button>
|
||||||
<button onclick="output(showTime())">Show current time</button>
|
<button onclick="output(showTime())">Show current time</button>
|
||||||
|
<button onclick="cert()">Cert request</button>
|
||||||
<input id="timeSkewInput"/>
|
<input id="timeSkewInput"/>
|
||||||
<button onclick="addToTimeSkew()">timeSkew offset</button>
|
<button onclick="addToTimeSkew()">timeSkew offset</button>
|
||||||
<button onclick="refreshTimeSkew()">refresh timeSkew</button>
|
<button onclick="refreshTimeSkew()">refresh timeSkew</button>
|
||||||
|
@ -215,6 +216,30 @@ TimeSkew: <div id="timeSkew"></div>
|
||||||
req.send();
|
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;
|
var keycloak;
|
||||||
|
|
||||||
function keycloakInit() {
|
function keycloakInit() {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
<module>hello-world-authz-service</module>
|
<module>hello-world-authz-service</module>
|
||||||
<module>servlet-authz</module>
|
<module>servlet-authz</module>
|
||||||
<module>servlets</module>
|
<module>servlets</module>
|
||||||
|
<module>app-profile-jee</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -77,6 +77,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
|
||||||
private WebElement createBearerRequest;
|
private WebElement createBearerRequest;
|
||||||
@FindBy(xpath = "//button[text() = 'Bearer to keycloak']")
|
@FindBy(xpath = "//button[text() = 'Bearer to keycloak']")
|
||||||
private WebElement createBearerRequestToKeycloakButton;
|
private WebElement createBearerRequestToKeycloakButton;
|
||||||
|
@FindBy(xpath = "//button[text() = 'Cert request']")
|
||||||
|
private WebElement certRequestButton;
|
||||||
@FindBy(xpath = "//button[text() = 'refresh timeSkew']")
|
@FindBy(xpath = "//button[text() = 'refresh timeSkew']")
|
||||||
private WebElement refreshTimeSkewButton;
|
private WebElement refreshTimeSkewButton;
|
||||||
|
|
||||||
|
@ -178,4 +180,8 @@ public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
|
||||||
public void refreshTimeSkew() {
|
public void refreshTimeSkew() {
|
||||||
refreshTimeSkewButton.click();
|
refreshTimeSkewButton.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendCertRequest() {
|
||||||
|
certRequestButton.click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -37,6 +37,11 @@ public interface TestOIDCEndpointsApplicationResource {
|
||||||
@Path("/generate-keys")
|
@Path("/generate-keys")
|
||||||
Map<String, String> generateKeys();
|
Map<String, String> generateKeys();
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/get-keys-as-pem")
|
||||||
|
Map<String, String> getKeysAsPem();
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -74,6 +74,7 @@ public final class WaitUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void pause(long millis) {
|
public static void pause(long millis) {
|
||||||
|
if (millis > 0) {
|
||||||
log.info("Wait: " + millis + "ms");
|
log.info("Wait: " + millis + "ms");
|
||||||
try {
|
try {
|
||||||
Thread.sleep(millis);
|
Thread.sleep(millis);
|
||||||
|
@ -82,11 +83,12 @@ public final class WaitUtils {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for page to finish any pending redirects, REST API requests etc.
|
* 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
|
* Because Keycloak's Admin Console is a single-page application, we need to
|
||||||
* the page is fully loaded
|
* take extra steps to ensure the page is fully loaded
|
||||||
*
|
*
|
||||||
* @param driver
|
* @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
|
// Checks if the document is ready and asks AngularJS, if present, whether there are any REST API requests
|
||||||
// in progress
|
// in progress
|
||||||
wait.until(javaScriptThrowsNoExceptions(
|
wait.until(javaScriptThrowsNoExceptions(
|
||||||
"if (document.readyState !== 'complete' " +
|
"if (document.readyState !== 'complete' "
|
||||||
"|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {" +
|
+ "|| (typeof angular !== 'undefined' && angular.element(document.body).injector().get('$http').pendingRequests.length !== 0)) {"
|
||||||
"throw \"Not ready\";" +
|
+ "throw \"Not ready\";"
|
||||||
"}"));
|
+ "}"));
|
||||||
}
|
} catch (TimeoutException e) {
|
||||||
catch (TimeoutException e) {
|
|
||||||
// Sometimes, for no obvious reason, the browser/JS doesn't set document.readyState to 'complete' correctly
|
// 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
|
// but that's no reason to let the test fail; after the timeout the page is surely fully loaded
|
||||||
log.warn("waitForPageToLoad time exceeded!");
|
log.warn("waitForPageToLoad time exceeded!");
|
||||||
|
|
|
@ -189,6 +189,15 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("\"username\": \"user\"");
|
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("\"username\": \"user\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCertEndpoint() {
|
||||||
|
logInAndInit("standard");
|
||||||
|
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
|
||||||
|
|
||||||
|
jsConsoleTestAppPage.sendCertRequest();
|
||||||
|
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void grantBrowserBasedApp() {
|
public void grantBrowserBasedApp() {
|
||||||
testRealmPage.setAuthRealm(EXAMPLE);
|
testRealmPage.setAuthRealm(EXAMPLE);
|
||||||
|
@ -322,6 +331,16 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Access token expired");
|
waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Access token expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void implicitFlowCertEndpoint() {
|
||||||
|
setImplicitFlowForClient();
|
||||||
|
logInAndInit("implicit");
|
||||||
|
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
|
||||||
|
|
||||||
|
jsConsoleTestAppPage.sendCertRequest();
|
||||||
|
waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBearerRequest() {
|
public void testBearerRequest() {
|
||||||
jsConsoleTestAppPage.navigateTo();
|
jsConsoleTestAppPage.navigateTo();
|
||||||
|
@ -406,6 +425,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
|
||||||
jsConsoleTestAppPage.setFlow(flow);
|
jsConsoleTestAppPage.setFlow(flow);
|
||||||
jsConsoleTestAppPage.init();
|
jsConsoleTestAppPage.init();
|
||||||
jsConsoleTestAppPage.logIn();
|
jsConsoleTestAppPage.logIn();
|
||||||
|
waitUntilElement(By.xpath("//body")).is().present();
|
||||||
testRealmLoginPage.form().login(user, "password");
|
testRealmLoginPage.form().login(user, "password");
|
||||||
jsConsoleTestAppPage.setFlow(flow);
|
jsConsoleTestAppPage.setFlow(flow);
|
||||||
jsConsoleTestAppPage.init();
|
jsConsoleTestAppPage.init();
|
||||||
|
|
|
@ -13,13 +13,6 @@ import org.keycloak.testsuite.arquillian.annotation.UseServletFilter;
|
||||||
public abstract class AbstractDemoFilterServletAdapterTest extends AbstractDemoServletsAdapterTest {
|
public abstract class AbstractDemoFilterServletAdapterTest extends AbstractDemoServletsAdapterTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
@Ignore
|
|
||||||
public void testCustomerPortalWithSubsystemSettings() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
@Ignore
|
@Ignore
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.keycloak.testsuite.adapter.servlet;
|
package org.keycloak.testsuite.adapter.servlet;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
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.common.util.Time;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.keys.KeyProvider;
|
import org.keycloak.keys.KeyProvider;
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
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.CustomerDb;
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerDbErrorPage;
|
import org.keycloak.testsuite.adapter.page.CustomerDbErrorPage;
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerPortal;
|
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.InputPortal;
|
||||||
import org.keycloak.testsuite.adapter.page.ProductPortal;
|
import org.keycloak.testsuite.adapter.page.ProductPortal;
|
||||||
import org.keycloak.testsuite.adapter.page.SecurePortal;
|
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.auth.page.login.OAuthGrant;
|
||||||
import org.keycloak.testsuite.console.page.events.Config;
|
import org.keycloak.testsuite.console.page.events.Config;
|
||||||
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
import org.keycloak.testsuite.console.page.events.LoginEvents;
|
||||||
import org.keycloak.testsuite.util.URLAssert;
|
|
||||||
import org.keycloak.testsuite.util.URLUtils;
|
import org.keycloak.testsuite.util.URLUtils;
|
||||||
import org.keycloak.util.BasicAuthHelper;
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
import org.openqa.selenium.By;
|
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.assertFalse;
|
||||||
import static org.junit.Assert.assertNotEquals;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import org.keycloak.testsuite.adapter.page.CustomerPortalNoConf;
|
import org.keycloak.testsuite.adapter.page.CustomerPortalNoConf;
|
||||||
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
|
||||||
|
@ -107,8 +102,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
||||||
@Page
|
@Page
|
||||||
private CustomerPortalNoConf customerPortalNoConf;
|
private CustomerPortalNoConf customerPortalNoConf;
|
||||||
@Page
|
@Page
|
||||||
private CustomerPortalSubsystem customerPortalSubsystem;
|
|
||||||
@Page
|
|
||||||
private SecurePortal securePortal;
|
private SecurePortal securePortal;
|
||||||
@Page
|
@Page
|
||||||
private CustomerDb customerDb;
|
private CustomerDb customerDb;
|
||||||
|
@ -141,11 +134,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
||||||
return servletDeployment(CustomerPortalNoConf.DEPLOYMENT_NAME, CustomerServletNoConf.class, ErrorServlet.class);
|
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)
|
@Deployment(name = SecurePortal.DEPLOYMENT_NAME)
|
||||||
protected static WebArchive securePortal() {
|
protected static WebArchive securePortal() {
|
||||||
return servletDeployment(SecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
|
return servletDeployment(SecurePortal.DEPLOYMENT_NAME, CallAuthenticatedServlet.class);
|
||||||
|
@ -197,14 +185,6 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void testSavedPostRequest() throws InterruptedException {
|
public void testSavedPostRequest() throws InterruptedException {
|
||||||
// test login to customer-portal which does a bearer request to customer-db
|
// test login to customer-portal which does a bearer request to customer-db
|
||||||
|
@ -851,5 +831,4 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
|
||||||
assertTrue(pageSource.contains("Forbidden") || pageSource.contains("HTTP Status 401"));
|
assertTrue(pageSource.contains("Forbidden") || pageSource.contains("HTTP Status 401"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -29,6 +29,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
|
||||||
salesPostSigPersistentServletPage.checkRoles(true);
|
salesPostSigPersistentServletPage.checkRoles(true);
|
||||||
salesPostSigTransientServletPage.checkRoles(true);
|
salesPostSigTransientServletPage.checkRoles(true);
|
||||||
salesPostAssertionAndResponseSigPage.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
|
//using endpoint instead of query param because we are not able to put query param to IDP initiated login
|
||||||
employee2ServletPage.navigateTo();
|
employee2ServletPage.navigateTo();
|
||||||
|
@ -54,6 +57,9 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
|
||||||
salesPostSigEmailServletPage.checkRoles(false);
|
salesPostSigEmailServletPage.checkRoles(false);
|
||||||
salesPostSigPersistentServletPage.checkRoles(false);
|
salesPostSigPersistentServletPage.checkRoles(false);
|
||||||
salesPostSigTransientServletPage.checkRoles(false);
|
salesPostSigTransientServletPage.checkRoles(false);
|
||||||
|
employeeSigPostNoIdpKeyServletPage.checkRoles(false);
|
||||||
|
employeeSigRedirNoIdpKeyServletPage.checkRoles(false);
|
||||||
|
employeeSigRedirOptNoIdpKeyServletPage.checkRoles(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -28,6 +28,8 @@ import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.keys.KeyProvider;
|
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.protocol.oidc.OIDCLoginProtocolService;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||||
|
@ -107,15 +109,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
@Test
|
@Test
|
||||||
public void testSignatureVerificationJwksUrl() throws Exception {
|
public void testSignatureVerificationJwksUrl() throws Exception {
|
||||||
// Configure OIDC identity provider with JWKS URL
|
// Configure OIDC identity provider with JWKS URL
|
||||||
IdentityProviderRepresentation idpRep = getIdentityProvider();
|
updateIdentityProviderWithJwksUrl();
|
||||||
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);
|
|
||||||
|
|
||||||
// Check that user is able to login
|
// Check that user is able to login
|
||||||
logInAsUserInIDPForFirstTime();
|
logInAsUserInIDPForFirstTime();
|
||||||
|
@ -139,6 +133,19 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
assertLoggedInAccountManagement();
|
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
|
@Test
|
||||||
public void testSignatureVerificationHardcodedPublicKey() throws Exception {
|
public void testSignatureVerificationHardcodedPublicKey() throws Exception {
|
||||||
|
@ -178,23 +185,17 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
|
||||||
@Test
|
@Test
|
||||||
public void testClearKeysCache() throws Exception {
|
public void testClearKeysCache() throws Exception {
|
||||||
// Configure OIDC identity provider with JWKS URL
|
// Configure OIDC identity provider with JWKS URL
|
||||||
IdentityProviderRepresentation idpRep = getIdentityProvider();
|
updateIdentityProviderWithJwksUrl();
|
||||||
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);
|
|
||||||
|
|
||||||
// Check that user is able to login
|
// Check that user is able to login
|
||||||
logInAsUserInIDPForFirstTime();
|
logInAsUserInIDPForFirstTime();
|
||||||
assertLoggedInAccountManagement();
|
assertLoggedInAccountManagement();
|
||||||
|
|
||||||
|
logoutFromRealm(bc.consumerRealmName());
|
||||||
|
|
||||||
// Check that key is cached
|
// 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);
|
TestingCacheResource cache = testingClient.testing(bc.consumerRealmName()).cache(InfinispanConnectionProvider.KEYS_CACHE_NAME);
|
||||||
Assert.assertTrue(cache.contains(expectedCacheKey));
|
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() {
|
private void rotateKeys() {
|
||||||
String activeKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
|
String activeKid = providerRealm().keys().getKeyMetadata().getActive().get("RSA");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
RealmRepresentation rep = new RealmRepresentation();
|
RealmRepresentation rep = new RealmRepresentation();
|
||||||
rep.setEnabled(true);
|
rep.setEnabled(true);
|
||||||
|
rep.setId(REALM_NAME);
|
||||||
rep.setRealm(REALM_NAME);
|
rep.setRealm(REALM_NAME);
|
||||||
rep.setUsers(new LinkedList<UserRepresentation>());
|
rep.setUsers(new LinkedList<UserRepresentation>());
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.testsuite.client;
|
package org.keycloak.testsuite.client;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -40,9 +41,14 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||||
import org.keycloak.client.registration.Auth;
|
import org.keycloak.client.registration.Auth;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.constants.ServiceUrlConstants;
|
import org.keycloak.constants.ServiceUrlConstants;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
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.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.keys.PublicKeyStorageUtils;
|
||||||
|
import org.keycloak.keys.loader.PublicKeyStorageManager;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
|
@ -139,6 +145,16 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
||||||
// The "kid" is set manually to some custom value
|
// The "kid" is set manually to some custom value
|
||||||
@Test
|
@Test
|
||||||
public void createClientWithJWKS_customKid() throws Exception {
|
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();
|
OIDCClientRepresentation clientRep = createRep();
|
||||||
|
|
||||||
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
|
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||||
|
@ -146,20 +162,84 @@ public class OIDCJwksClientRegistrationTest extends AbstractClientRegistrationTe
|
||||||
|
|
||||||
// Generate keys for client
|
// Generate keys for client
|
||||||
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
|
||||||
Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
|
oidcClientEndpointsResource.generateKeys();
|
||||||
|
|
||||||
JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
|
JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
|
||||||
|
|
||||||
// Override kid with custom value
|
// Override kid with custom value
|
||||||
keySet.getKeys()[0].setKeyId("a1");
|
keySet.getKeys()[0].setKeyId(kid);
|
||||||
clientRep.setJwks(keySet);
|
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
|
// Tries to authenticate client with privateKey JWT
|
||||||
assertAuthenticateClientSuccess(generatedKeys, response, "a1");
|
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
|
@Test
|
||||||
public void createClientWithJWKSURI() throws Exception {
|
public void createClientWithJWKSURI() throws Exception {
|
||||||
OIDCClientRepresentation clientRep = createRep();
|
OIDCClientRepresentation clientRep = createRep();
|
||||||
|
|
|
@ -175,6 +175,16 @@
|
||||||
],
|
],
|
||||||
"secret": "password"
|
"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",
|
"clientId": "secure-portal",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
|
@ -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" ]
|
||||||
|
}
|
||||||
|
} ]
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-as7")
|
@AppServerContainer("app-server-as7")
|
||||||
public class AS7OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class AS7OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,16 @@
|
||||||
<resource>customer-portal-subsystem</resource>
|
<resource>customer-portal-subsystem</resource>
|
||||||
<credential name="secret">password</credential>
|
<credential name="secret">password</credential>
|
||||||
</secure-deployment>
|
</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:copy>
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-eap")
|
@AppServerContainer("app-server-eap")
|
||||||
public class EAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class EAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<artifactId>integration-arquillian-tests-adapters-eap6-fuse</artifactId>
|
<artifactId>integration-arquillian-tests-adapters-eap6-fuse</artifactId>
|
||||||
|
|
||||||
<name>Adapter Tests - JBoss - EAP 6</name>
|
<name>Adapter Tests - JBoss - EAP 6 Fuse</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<app.server>eap6-fuse</app.server>
|
<app.server>eap6-fuse</app.server>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-eap6")
|
@AppServerContainer("app-server-eap6")
|
||||||
public class EAP6OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class EAP6OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,8 @@
|
||||||
<properties>
|
<properties>
|
||||||
<common.resources>${project.parent.basedir}/common</common.resources>
|
<common.resources>${project.parent.basedir}/common</common.resources>
|
||||||
<app.server.type>managed</app.server.type>
|
<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>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -57,12 +59,12 @@
|
||||||
<includes>
|
<includes>
|
||||||
<include>standalone.xml</include>
|
<include>standalone.xml</include>
|
||||||
</includes>
|
</includes>
|
||||||
<stylesheet>${common.resources}/xslt/keycloak-subsystem.xsl</stylesheet>
|
<stylesheet>${common.resources}/xslt/${keycloak.subsystem.xsl}</stylesheet>
|
||||||
<outputDir>${app.server.home}/standalone/configuration</outputDir>
|
<outputDir>${app.server.home}/standalone/configuration</outputDir>
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter>
|
<parameter>
|
||||||
<name>auth-server-host</name>
|
<name>auth-server-host</name>
|
||||||
<value>http://localhost:${auth.server.http.port}</value>
|
<value>http://localhost:${auth.server.actual.http.port}</value>
|
||||||
</parameter>
|
</parameter>
|
||||||
</parameters>
|
</parameters>
|
||||||
</transformationSet>
|
</transformationSet>
|
||||||
|
@ -83,41 +85,17 @@
|
||||||
<value>true</value>
|
<value>true</value>
|
||||||
</property>
|
</property>
|
||||||
</activation>
|
</activation>
|
||||||
<build>
|
<properties>
|
||||||
<plugins>
|
<!-- one realm definition for each secure-deployment -->
|
||||||
<plugin>
|
<auth.server.actual.http.port>${auth.server.https.port}</auth.server.actual.http.port>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
</properties>
|
||||||
<artifactId>xml-maven-plugin</artifactId>
|
</profile>
|
||||||
<executions>
|
<profile>
|
||||||
<execution>
|
<id>keycloak-subsystem-separate-realm</id>
|
||||||
<id>configure-keycloak-subsystem</id>
|
<properties>
|
||||||
<phase>process-test-resources</phase>
|
<!-- single realm definition, multiple secure-deployments -->
|
||||||
<goals>
|
<keycloak.subsystem.xsl>keycloak-subsystem_separate-realm-def.xsl</keycloak.subsystem.xsl>
|
||||||
<goal>transform</goal>
|
</properties>
|
||||||
</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>
|
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
<id>adapter-test-jboss-submodules</id>
|
<id>adapter-test-jboss-submodules</id>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
package org.keycloak.testsuite.adapter;
|
||||||
|
|
||||||
import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
|
import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
public class RelativeEAPOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class RelativeEAPOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
package org.keycloak.testsuite.adapter;
|
||||||
|
|
||||||
import org.keycloak.testsuite.adapter.servlet.AbstractDemoServletsAdapterTest;
|
import org.keycloak.testsuite.adapter.servlet.AbstractJBossOIDCServletsAdapterTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
public class RelativeWildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class RelativeWildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.keycloak.testsuite</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>integration-arquillian-tests-adapters-jboss</artifactId>
|
<artifactId>integration-arquillian-tests-adapters-jboss</artifactId>
|
||||||
<version>2.1.0-SNAPSHOT</version>
|
<version>2.4.1.Final-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>integration-arquillian-tests-adapters-remote</artifactId>
|
<artifactId>integration-arquillian-tests-adapters-remote</artifactId>
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<artifactItems>
|
<artifactItems>
|
||||||
<artifactItem>
|
<artifactItem>
|
||||||
<groupId>org.keycloak.quickstart</groupId>
|
<groupId>org.keycloak.testsuite</groupId>
|
||||||
<artifactId>keycloak-quickstart-app-profile-jee</artifactId>
|
<artifactId>keycloak-test-app-profile-jee</artifactId>
|
||||||
<version>0.5-SNAPSHOT</version>
|
<version>${project.version}</version>
|
||||||
<type>war</type>
|
<type>war</type>
|
||||||
</artifactItem>
|
</artifactItem>
|
||||||
</artifactItems>
|
</artifactItems>
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(HttpClientLoginLogoutPerfTest.class);
|
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 securedUrl;
|
||||||
private String logoutUrl;
|
private String logoutUrl;
|
||||||
|
@ -72,18 +72,18 @@ public class HttpClientLoginLogoutPerfTest extends HttpClientPerformanceTest {
|
||||||
|
|
||||||
@Deployment(name = AppProfileJEE.DEPLOYMENT_NAME)
|
@Deployment(name = AppProfileJEE.DEPLOYMENT_NAME)
|
||||||
private static WebArchive appProfileJEE() throws IOException {
|
private static WebArchive appProfileJEE() throws IOException {
|
||||||
return warDeployment("keycloak-quickstart-app-profile-jee-0.5-SNAPSHOT");
|
return exampleDeployment("keycloak-test-app-profile-jee");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDefaultPageUriParameters() {
|
public void setDefaultPageUriParameters() {
|
||||||
super.setDefaultPageUriParameters();
|
super.setDefaultPageUriParameters();
|
||||||
testRealmPage.setAuthRealm(EXAMPLES);
|
testRealmPage.setAuthRealm(TEST_REALM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
|
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
RealmRepresentation examplesRealm = loadRealm("/examples-realm.json");
|
RealmRepresentation examplesRealm = loadRealm("/test-realm.json");
|
||||||
examplesRealm.setPasswordPolicy("hashIterations(" + PASSWORD_HASH_ITERATIONS + ")");
|
examplesRealm.setPasswordPolicy("hashIterations(" + PASSWORD_HASH_ITERATIONS + ")");
|
||||||
testRealms.add(examplesRealm);
|
testRealms.add(examplesRealm);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-wildfly")
|
@AppServerContainer("app-server-wildfly")
|
||||||
public class WildflyOIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class WildflyOIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-wildfly8")
|
@AppServerContainer("app-server-wildfly8")
|
||||||
public class Wildfly8OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class Wildfly8OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.keycloak.testsuite.adapter;
|
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;
|
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +8,6 @@ import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||||
* @author tkyjovsk
|
* @author tkyjovsk
|
||||||
*/
|
*/
|
||||||
@AppServerContainer("app-server-wildfly9")
|
@AppServerContainer("app-server-wildfly9")
|
||||||
public class Wildfly9OIDCAdapterTest extends AbstractDemoServletsAdapterTest {
|
public class Wildfly9OIDCAdapterTest extends AbstractJBossOIDCServletsAdapterTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.openqa.selenium.WebElement;
|
||||||
import org.openqa.selenium.support.FindBy;
|
import org.openqa.selenium.support.FindBy;
|
||||||
import org.openqa.selenium.support.ui.Select;
|
import org.openqa.selenium.support.ui.Select;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
|
||||||
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,8 +34,9 @@ public class PasswordPolicy extends Authentication {
|
||||||
public void addPolicy(Type policy, String value) {
|
public void addPolicy(Type policy, String value) {
|
||||||
waitUntilElement(addPolicySelectElement).is().present();
|
waitUntilElement(addPolicySelectElement).is().present();
|
||||||
addPolicySelect.selectByVisibleText(policy.getName());
|
addPolicySelect.selectByVisibleText(policy.getName());
|
||||||
setPolicyValue(policy, value);
|
if (value != null) {setPolicyValue(policy, value);}
|
||||||
primaryButton.click();
|
primaryButton.click();
|
||||||
|
waitForPageToLoad(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,15 +45,13 @@ public class PasswordPolicy extends Authentication {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPolicy(Type policy) {
|
public void addPolicy(Type policy) {
|
||||||
addPolicySelect.selectByVisibleText(policy.getName());
|
addPolicy(policy, null);
|
||||||
primaryButton.click();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePolicy(Type policy) {
|
public void removePolicy(Type policy) {
|
||||||
getPolicyRow(policy).findElement(By.cssSelector("td.kc-action-cell")).click();
|
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) {
|
public void editPolicy(Type policy, int value) {
|
||||||
|
@ -61,6 +61,7 @@ public class PasswordPolicy extends Authentication {
|
||||||
public void editPolicy(Type policy, String value) {
|
public void editPolicy(Type policy, String value) {
|
||||||
setPolicyValue(policy, value);
|
setPolicyValue(policy, value);
|
||||||
primaryButton.click();
|
primaryButton.click();
|
||||||
|
waitForPageToLoad(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPolicyValue(Type policy, String value) {
|
private void setPolicyValue(Type policy, String value) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class PasswordPolicyTest extends AbstractConsoleTest {
|
||||||
public void testAddAndRemovePolicy() {
|
public void testAddAndRemovePolicy() {
|
||||||
passwordPolicyPage.navigateTo();
|
passwordPolicyPage.navigateTo();
|
||||||
passwordPolicyPage.addPolicy(DIGITS, 5);
|
passwordPolicyPage.addPolicy(DIGITS, 5);
|
||||||
|
assertAlertSuccess();
|
||||||
passwordPolicyPage.removePolicy(DIGITS);
|
passwordPolicyPage.removePolicy(DIGITS);
|
||||||
assertAlertSuccess();
|
assertAlertSuccess();
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,6 @@ public class SSSDTest extends AbstractKeycloakTest {
|
||||||
driver.navigate().to(getAccountUrl());
|
driver.navigate().to(getAccountUrl());
|
||||||
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
||||||
accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
|
accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
|
||||||
|
|
||||||
Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction());
|
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());
|
driver.navigate().to(getAccountUrl());
|
||||||
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
|
||||||
accountLoginPage.login(USERNAME, PASSWORD);
|
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();
|
testUserGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,7 +209,7 @@
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.wildfly</groupId>
|
<groupId>org.wildfly.arquillian</groupId>
|
||||||
<artifactId>wildfly-arquillian-container-remote</artifactId>
|
<artifactId>wildfly-arquillian-container-remote</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -637,6 +637,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
|
||||||
console.log('UserFederationCtrl ++++****');
|
console.log('UserFederationCtrl ++++****');
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
|
$scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
|
||||||
|
$scope.instancesLoaded = false;
|
||||||
|
|
||||||
if (!$scope.providers) $scope.providers = [];
|
if (!$scope.providers) $scope.providers = [];
|
||||||
|
|
||||||
|
@ -716,7 +717,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
|
||||||
data[i].isUserFederationProvider = true;
|
data[i].isUserFederationProvider = true;
|
||||||
$scope.instances.push(data[i]);
|
$scope.instances.push(data[i]);
|
||||||
}
|
}
|
||||||
|
$scope.instancesLoaded = true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<span>{{:: 'user-federation' | translate}}</span>
|
<span>{{:: 'user-federation' | translate}}</span>
|
||||||
</h1>
|
</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">
|
<div class="blank-slate-pf-icon">
|
||||||
<span class="fa fa-database"></span>
|
<span class="fa fa-database"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -85,7 +85,7 @@ public final class KeycloakAdapterConfigService {
|
||||||
// be where the Keycloak server's Config interface expects it to be.
|
// be where the Keycloak server's Config interface expects it to be.
|
||||||
|
|
||||||
private void massageScheduledTaskInterval(ModelNode copy) {
|
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");
|
ModelNode taskInterval = copy.remove("scheduled-task-interval");
|
||||||
copy.get("scheduled", "interval").set(taskInterval);
|
copy.get("scheduled", "interval").set(taskInterval);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue