Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
3ee86fedc7
35 changed files with 691 additions and 507 deletions
|
@ -364,26 +364,26 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
|
||||
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
|
||||
try {
|
||||
validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
|
||||
} catch (VerificationException e) {
|
||||
log.error("Failed to verify saml assertion signature", e);
|
||||
if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
|
||||
log.error("Failed to verify saml assertion signature");
|
||||
|
||||
challenge = new AuthChallenge() {
|
||||
challenge = new AuthChallenge() {
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||
exchange.getRequest().setError(error);
|
||||
exchange.getResponse().sendError(403);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
|
||||
exchange.getRequest().setError(error);
|
||||
exchange.getResponse().sendError(403);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 403;
|
||||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 403;
|
||||
}
|
||||
};
|
||||
return AuthOutcome.FAILED;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing validation of SAML assertion: " + e.getMessage());
|
||||
challenge = new AuthChallenge() {
|
||||
|
@ -504,19 +504,16 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
|
|||
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
|
||||
}
|
||||
|
||||
private Document buildAssertionDocument(final SAMLDocumentHolder responseHolder, AssertionType assertion) throws ConfigurationException, ProcessingException {
|
||||
Element encryptedAssertion = org.keycloak.saml.common.util.DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||
private Element getAssertionFromResponse(final SAMLDocumentHolder responseHolder) throws ConfigurationException, ProcessingException {
|
||||
Element encryptedAssertion = DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||
if (encryptedAssertion != null) {
|
||||
// encrypted assertion.
|
||||
// We'll need to decrypt it first.
|
||||
Document encryptedAssertionDocument = DocumentUtil.createDocument();
|
||||
encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
|
||||
Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
|
||||
Document assertionDocument = DocumentUtil.createDocument();
|
||||
assertionDocument.appendChild(assertionDocument.importNode(assertionElement, true));
|
||||
return assertionDocument;
|
||||
return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
|
||||
}
|
||||
return AssertionUtil.asDocument(assertion);
|
||||
return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
|
||||
}
|
||||
|
||||
private String getAttributeValue(Object attrValue) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.keycloak.models.ClientInitialAccessModel;
|
||||
import org.keycloak.models.cache.infinispan.events.InvalidationEvent;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
|
@ -1059,4 +1060,34 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
return adapter;
|
||||
}
|
||||
|
||||
// Don't cache ClientInitialAccessModel for now
|
||||
@Override
|
||||
public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
|
||||
return getDelegate().createClientInitialAccessModel(realm, expiration, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
|
||||
return getDelegate().getClientInitialAccessModel(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientInitialAccessModel(RealmModel realm, String id) {
|
||||
getDelegate().removeClientInitialAccessModel(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
|
||||
return getDelegate().listClientInitialAccess(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredClientInitialAccess() {
|
||||
getDelegate().removeExpiredClientInitialAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
|
||||
getDelegate().decreaseRemainingCount(realm, clientInitialAccess);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,86 +0,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.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.keycloak.models.ClientInitialAccessModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientInitialAccessAdapter implements ClientInitialAccessModel {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final InfinispanUserSessionProvider provider;
|
||||
private final Cache<String, SessionEntity> cache;
|
||||
private final RealmModel realm;
|
||||
private final ClientInitialAccessEntity entity;
|
||||
|
||||
public ClientInitialAccessAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientInitialAccessEntity entity) {
|
||||
this.session = session;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
this.realm = realm;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTimestamp() {
|
||||
return entity.getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpiration() {
|
||||
return entity.getExpiration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return entity.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingCount() {
|
||||
return entity.getRemainingCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseRemainingCount() {
|
||||
entity.setRemainingCount(entity.getRemainingCount() - 1);
|
||||
update();
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,6 @@ import org.infinispan.CacheStream;
|
|||
import org.infinispan.context.Flag;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ClientInitialAccessModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -32,23 +31,19 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.UserSessionProvider;
|
||||
import org.keycloak.models.session.UserSessionPersisterProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
|
||||
import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.Comparators;
|
||||
import org.keycloak.models.sessions.infinispan.stream.Mappers;
|
||||
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
|
||||
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -271,7 +266,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
log.debugf("Removing expired sessions");
|
||||
removeExpiredUserSessions(realm);
|
||||
removeExpiredOfflineUserSessions(realm);
|
||||
removeExpiredClientInitialAccess(realm);
|
||||
}
|
||||
|
||||
private void removeExpiredUserSessions(RealmModel realm) {
|
||||
|
@ -317,14 +311,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
|
||||
}
|
||||
|
||||
private void removeExpiredClientInitialAccess(RealmModel realm) {
|
||||
Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
|
||||
.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
|
||||
while (itr.hasNext()) {
|
||||
tx.remove(sessionCache, itr.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUserSessions(RealmModel realm) {
|
||||
removeUserSessions(realm, false);
|
||||
|
@ -417,19 +403,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return models;
|
||||
}
|
||||
|
||||
List<ClientInitialAccessModel> wrapClientInitialAccess(RealmModel realm, Collection<ClientInitialAccessEntity> entities) {
|
||||
List<ClientInitialAccessModel> models = new LinkedList<>();
|
||||
for (ClientInitialAccessEntity e : entities) {
|
||||
models.add(wrap(realm, e));
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
|
||||
Cache<String, SessionEntity> cache = getCache(false);
|
||||
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
|
||||
}
|
||||
|
||||
UserLoginFailureModel wrap(LoginFailureKey key, LoginFailureEntity entity) {
|
||||
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
|
||||
}
|
||||
|
@ -565,48 +538,4 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
|
|||
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
|
||||
String id = KeycloakModelUtils.generateId();
|
||||
|
||||
ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
|
||||
entity.setId(id);
|
||||
entity.setRealm(realm.getId());
|
||||
entity.setTimestamp(Time.currentTime());
|
||||
entity.setExpiration(expiration);
|
||||
entity.setCount(count);
|
||||
entity.setRemainingCount(count);
|
||||
|
||||
tx.put(sessionCache, id, entity);
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
|
||||
Cache<String, SessionEntity> cache = getCache(false);
|
||||
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) tx.get(cache, id); // Chance created in this transaction
|
||||
|
||||
if (entity == null) {
|
||||
entity = (ClientInitialAccessEntity) cache.get(id);
|
||||
}
|
||||
|
||||
return wrap(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientInitialAccessModel(RealmModel realm, String id) {
|
||||
tx.remove(getCache(false), id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
|
||||
Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId())).iterator();
|
||||
List<ClientInitialAccessModel> list = new LinkedList<>();
|
||||
while (itr.hasNext()) {
|
||||
list.add(wrap(realm, (ClientInitialAccessEntity) itr.next().getValue()));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,65 +0,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.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientInitialAccessEntity extends SessionEntity {
|
||||
|
||||
private int timestamp;
|
||||
|
||||
private int expires;
|
||||
|
||||
private int count;
|
||||
|
||||
private int remainingCount;
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getExpiration() {
|
||||
return expires;
|
||||
}
|
||||
|
||||
public void setExpiration(int expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int getRemainingCount() {
|
||||
return remainingCount;
|
||||
}
|
||||
|
||||
public void setRemainingCount(int remainingCount) {
|
||||
this.remainingCount = remainingCount;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,96 +0,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.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.mapreduce;
|
||||
|
||||
import org.infinispan.distexec.mapreduce.Collector;
|
||||
import org.infinispan.distexec.mapreduce.Mapper;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientInitialAccessMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
|
||||
|
||||
public ClientInitialAccessMapper(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private enum EmitValue {
|
||||
KEY, ENTITY
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
private EmitValue emit = EmitValue.ENTITY;
|
||||
|
||||
private Integer expired;
|
||||
|
||||
public static ClientInitialAccessMapper create(String realm) {
|
||||
return new ClientInitialAccessMapper(realm);
|
||||
}
|
||||
|
||||
public ClientInitialAccessMapper emitKey() {
|
||||
emit = EmitValue.KEY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientInitialAccessMapper expired(int time) {
|
||||
this.expired = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(String key, SessionEntity e, Collector collector) {
|
||||
if (!realm.equals(e.getRealm())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(e instanceof ClientInitialAccessEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
|
||||
|
||||
boolean include = false;
|
||||
|
||||
if (expired != null) {
|
||||
if (entity.getRemainingCount() <= 0) {
|
||||
include = true;
|
||||
} else if (entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < expired) {
|
||||
include = true;
|
||||
}
|
||||
} else {
|
||||
include = true;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
switch (emit) {
|
||||
case KEY:
|
||||
collector.emit(key, key);
|
||||
break;
|
||||
case ENTITY:
|
||||
collector.emit(key, entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,76 +0,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.
|
||||
*/
|
||||
|
||||
package org.keycloak.models.sessions.infinispan.stream;
|
||||
|
||||
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class ClientInitialAccessPredicate implements Predicate<Map.Entry<String, SessionEntity>>, Serializable {
|
||||
|
||||
public ClientInitialAccessPredicate(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
private String realm;
|
||||
|
||||
private Integer expired;
|
||||
|
||||
public static ClientInitialAccessPredicate create(String realm) {
|
||||
return new ClientInitialAccessPredicate(realm);
|
||||
}
|
||||
|
||||
public ClientInitialAccessPredicate expired(int time) {
|
||||
this.expired = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Map.Entry<String, SessionEntity> entry) {
|
||||
SessionEntity e = entry.getValue();
|
||||
|
||||
if (!realm.equals(e.getRealm())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(e instanceof ClientInitialAccessEntity)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientInitialAccessEntity entity = (ClientInitialAccessEntity) e;
|
||||
|
||||
if (expired != null) {
|
||||
if (entity.getRemainingCount() <= 0) {
|
||||
return true;
|
||||
} else if (entity.getExpiration() > 0 && (entity.getTimestamp() + entity.getExpiration()) < expired) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,8 +18,10 @@
|
|||
package org.keycloak.models.jpa;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.migration.MigrationModel;
|
||||
import org.keycloak.models.ClientInitialAccessModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientTemplateModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
|
@ -29,6 +31,7 @@ import org.keycloak.models.RealmProvider;
|
|||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.jpa.entities.ClientEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientInitialAccessEntity;
|
||||
import org.keycloak.models.jpa.entities.ClientTemplateEntity;
|
||||
import org.keycloak.models.jpa.entities.GroupEntity;
|
||||
import org.keycloak.models.jpa.entities.RealmEntity;
|
||||
|
@ -152,6 +155,8 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
removeRole(adapter, role);
|
||||
}
|
||||
|
||||
num = em.createNamedQuery("removeClientInitialAccessByRealm")
|
||||
.setParameter("realm", realm).executeUpdate();
|
||||
|
||||
em.remove(realm);
|
||||
|
||||
|
@ -537,4 +542,82 @@ public class JpaRealmProvider implements RealmProvider {
|
|||
ClientTemplateAdapter adapter = new ClientTemplateAdapter(realm, em, session, app);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count) {
|
||||
RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
|
||||
|
||||
ClientInitialAccessEntity entity = new ClientInitialAccessEntity();
|
||||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setRealm(realmEntity);
|
||||
|
||||
entity.setCount(count);
|
||||
entity.setRemainingCount(count);
|
||||
|
||||
int currentTime = Time.currentTime();
|
||||
entity.setTimestamp(currentTime);
|
||||
entity.setExpiration(expiration);
|
||||
|
||||
em.persist(entity);
|
||||
|
||||
return entityToModel(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id) {
|
||||
ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
} else {
|
||||
return entityToModel(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeClientInitialAccessModel(RealmModel realm, String id) {
|
||||
ClientInitialAccessEntity entity = em.find(ClientInitialAccessEntity.class, id);
|
||||
if (entity != null) {
|
||||
em.remove(entity);
|
||||
em.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm) {
|
||||
RealmEntity realmEntity = em.find(RealmEntity.class, realm.getId());
|
||||
|
||||
TypedQuery<ClientInitialAccessEntity> query = em.createNamedQuery("findClientInitialAccessByRealm", ClientInitialAccessEntity.class);
|
||||
query.setParameter("realm", realmEntity);
|
||||
List<ClientInitialAccessEntity> entities = query.getResultList();
|
||||
|
||||
return entities.stream()
|
||||
.map(entity -> entityToModel(entity))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredClientInitialAccess() {
|
||||
int currentTime = Time.currentTime();
|
||||
|
||||
em.createNamedQuery("removeExpiredClientInitialAccess")
|
||||
.setParameter("currentTime", currentTime)
|
||||
.executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess) {
|
||||
em.createNamedQuery("decreaseClientInitialAccessRemainingCount")
|
||||
.setParameter("id", clientInitialAccess.getId())
|
||||
.executeUpdate();
|
||||
}
|
||||
|
||||
private ClientInitialAccessModel entityToModel(ClientInitialAccessEntity entity) {
|
||||
ClientInitialAccessModel model = new ClientInitialAccessModel();
|
||||
model.setId(entity.getId());
|
||||
model.setCount(entity.getCount());
|
||||
model.setRemainingCount(entity.getRemainingCount());
|
||||
model.setExpiration(entity.getExpiration());
|
||||
model.setTimestamp(entity.getTimestamp());
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright 2017 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.models.jpa.entities;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="CLIENT_INITIAL_ACCESS")
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="findClientInitialAccessByRealm", query="select ia from ClientInitialAccessEntity ia where ia.realm = :realm order by timestamp"),
|
||||
@NamedQuery(name="removeClientInitialAccessByRealm", query="delete from ClientInitialAccessEntity ia where ia.realm = :realm"),
|
||||
@NamedQuery(name="removeExpiredClientInitialAccess", query="delete from ClientInitialAccessEntity ia where (ia.expiration > 0 and (ia.timestamp + ia.expiration) < :currentTime) or ia.remainingCount = 0"),
|
||||
@NamedQuery(name="decreaseClientInitialAccessRemainingCount", query="update ClientInitialAccessEntity ia set ia.remainingCount = ia.remainingCount - 1 where ia.id = :id")
|
||||
})
|
||||
public class ClientInitialAccessEntity {
|
||||
|
||||
@Id
|
||||
@Column(name="ID", length = 36)
|
||||
@Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
|
||||
protected String id;
|
||||
|
||||
@Column(name="TIMESTAMP")
|
||||
private int timestamp;
|
||||
|
||||
@Column(name="EXPIRATION")
|
||||
private int expiration;
|
||||
|
||||
@Column(name="COUNT")
|
||||
private int count;
|
||||
|
||||
@Column(name="REMAINING_COUNT")
|
||||
private int remainingCount;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "REALM_ID")
|
||||
protected RealmEntity realm;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(int expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int getRemainingCount() {
|
||||
return remainingCount;
|
||||
}
|
||||
|
||||
public void setRemainingCount(int remainingCount) {
|
||||
this.remainingCount = remainingCount;
|
||||
}
|
||||
|
||||
public RealmEntity getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(RealmEntity realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof ClientInitialAccessEntity)) return false;
|
||||
|
||||
ClientInitialAccessEntity that = (ClientInitialAccessEntity) o;
|
||||
|
||||
if (!id.equals(that.id)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id.hashCode();
|
||||
}
|
||||
}
|
|
@ -22,6 +22,22 @@
|
|||
<dropPrimaryKey constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
|
||||
<dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
|
||||
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
|
||||
|
||||
<createTable tableName="CLIENT_INITIAL_ACCESS">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
|
||||
<column name="TIMESTAMP" type="INT"/>
|
||||
<column name="EXPIRATION" type="INT"/>
|
||||
<column name="COUNT" type="INT"/>
|
||||
<column name="REMAINING_COUNT" type="INT"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="ID" constraintName="CNSTR_CLIENT_INIT_ACC_PK" tableName="CLIENT_INITIAL_ACCESS"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_INITIAL_ACCESS" constraintName="FK_CLIENT_INIT_ACC_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="glavoie@gmail.com" id="3.2.0.idx">
|
||||
|
@ -158,5 +174,9 @@
|
|||
<createIndex indexName="IDX_WEB_ORIG_CLIENT" tableName="WEB_ORIGINS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
|
||||
<createIndex indexName="IDX_CLIENT_INIT_ACC_REALM" tableName="CLIENT_INITIAL_ACCESS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.ClientInitialAccessEntity</class>
|
||||
|
||||
<!-- JpaAuditProviders -->
|
||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||
|
|
|
@ -52,6 +52,7 @@ import java.io.InputStream;
|
|||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Utility dealing with DOM
|
||||
|
@ -554,4 +555,33 @@ public class DocumentUtil {
|
|||
|
||||
return documentBuilderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a (direct) child {@linkplain Element} from the parent {@linkplain Element}.
|
||||
*
|
||||
* @param parent parent element
|
||||
* @param targetNamespace namespace URI
|
||||
* @param targetLocalName local name
|
||||
* @return a child element matching the target namespace and localname, where {@linkplain Element#getParentNode()} is the parent input parameter
|
||||
* @return
|
||||
*/
|
||||
|
||||
public static Element getDirectChildElement(Element parent, String targetNamespace, String targetLocalName) {
|
||||
Node child = parent.getFirstChild();
|
||||
|
||||
while(child != null) {
|
||||
if(child instanceof Element) {
|
||||
Element childElement = (Element)child;
|
||||
|
||||
String ns = childElement.getNamespaceURI();
|
||||
String localName = childElement.getLocalName();
|
||||
|
||||
if(Objects.equals(targetNamespace, ns) && Objects.equals(targetLocalName, localName)) {
|
||||
return childElement;
|
||||
}
|
||||
}
|
||||
child = child.getNextSibling();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -49,8 +49,6 @@ public class SAML2Signature {
|
|||
|
||||
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
|
||||
|
||||
private static final String ID_ATTRIBUTE_NAME = "ID";
|
||||
|
||||
private String signatureMethod = SignatureMethod.RSA_SHA1;
|
||||
|
||||
private String digestMethod = DigestMethod.SHA1;
|
||||
|
@ -156,7 +154,7 @@ public class SAML2Signature {
|
|||
*/
|
||||
public void signSAMLDocument(Document samlDocument, String keyName, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
|
||||
// Get the ID from the root
|
||||
String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
|
||||
String id = samlDocument.getDocumentElement().getAttribute(JBossSAMLConstants.ID.get());
|
||||
try {
|
||||
sign(samlDocument, id, keyName, keypair, canonicalizationMethodType);
|
||||
} catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
|
||||
|
@ -210,18 +208,20 @@ public class SAML2Signature {
|
|||
*
|
||||
* @param document SAML document to have its ID attribute configured.
|
||||
*/
|
||||
private void configureIdAttribute(Document document) {
|
||||
public static void configureIdAttribute(Document document) {
|
||||
// Estabilish the IDness of the ID attribute.
|
||||
document.getDocumentElement().setIdAttribute(ID_ATTRIBUTE_NAME, true);
|
||||
configureIdAttribute(document.getDocumentElement());
|
||||
|
||||
NodeList nodes = document.getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
||||
JBossSAMLConstants.ASSERTION.get());
|
||||
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node n = nodes.item(i);
|
||||
if (n instanceof Element) {
|
||||
((Element) n).setIdAttribute(ID_ATTRIBUTE_NAME, true);
|
||||
}
|
||||
configureIdAttribute((Element) nodes.item(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static void configureIdAttribute(Element element) {
|
||||
element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
|
||||
}
|
||||
|
||||
}
|
|
@ -49,11 +49,12 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
|||
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
|
||||
import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
|
||||
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
|
||||
|
||||
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.crypto.dsig.XMLSignature;
|
||||
import javax.xml.datatype.XMLGregorianCalendar;
|
||||
import javax.xml.namespace.QName;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -267,42 +268,56 @@ public class AssertionUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Given an assertion element, validate the signature
|
||||
* Given an {@linkplain Element}, validate the Signature direct child element
|
||||
*
|
||||
* @param assertionElement
|
||||
* @param element parent {@linkplain Element}
|
||||
* @param publicKey the {@link PublicKey}
|
||||
*
|
||||
* @return
|
||||
* @return true if signature is present and valid
|
||||
*/
|
||||
public static boolean isSignatureValid(Element assertionElement, PublicKey publicKey) {
|
||||
try {
|
||||
Document doc = DocumentUtil.createDocument();
|
||||
Node n = doc.importNode(assertionElement, true);
|
||||
doc.appendChild(n);
|
||||
|
||||
return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
|
||||
} catch (Exception e) {
|
||||
logger.signatureAssertionValidationError(e);
|
||||
}
|
||||
return false;
|
||||
public static boolean isSignatureValid(Element element, PublicKey publicKey) {
|
||||
return isSignatureValid(element, new HardcodedKeyLocator(publicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an assertion element, validate the signature.
|
||||
* Given an {@linkplain Element}, validate the Signature direct child element
|
||||
*
|
||||
* @param element parent {@linkplain Element}
|
||||
* @param keyLocator the {@link KeyLocator}
|
||||
*
|
||||
* @return true if signature is present and valid
|
||||
*/
|
||||
public static boolean isSignatureValid(Element assertionElement, KeyLocator keyLocator) {
|
||||
|
||||
public static boolean isSignatureValid(Element element, KeyLocator keyLocator) {
|
||||
try {
|
||||
Document doc = DocumentUtil.createDocument();
|
||||
Node n = doc.importNode(assertionElement, true);
|
||||
doc.appendChild(n);
|
||||
|
||||
return new SAML2Signature().validate(doc, keyLocator);
|
||||
SAML2Signature.configureIdAttribute(element);
|
||||
|
||||
Element signature = getSignature(element);
|
||||
if(signature != null) {
|
||||
return XMLSignatureUtil.validateSingleNode(signature, keyLocator);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.signatureAssertionValidationError(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Given an {@linkplain Element}, check if there is a Signature direct child element
|
||||
*
|
||||
* @param element parent {@linkplain Element}
|
||||
* @return true if signature is present
|
||||
*/
|
||||
|
||||
public static boolean isSignedElement(Element element) {
|
||||
return getSignature(element) != null;
|
||||
}
|
||||
|
||||
protected static Element getSignature(Element element) {
|
||||
return DocumentUtil.getDirectChildElement(element, XMLSignature.XMLNS, "Signature");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the assertion has expired
|
||||
*
|
||||
|
@ -570,8 +585,8 @@ public class AssertionUtil {
|
|||
|
||||
/**
|
||||
* This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
|
||||
*
|
||||
* It returns the assertion element as it was decrypted. This can be used in sginature verification.
|
||||
* @param responseType a response containg an encrypted assertion
|
||||
* @return the assertion element as it was decrypted. This can be used in signature verification.
|
||||
*/
|
||||
public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
|
||||
SAML2Response saml2Response = new SAML2Response();
|
||||
|
|
|
@ -468,7 +468,7 @@ public class XMLSignatureUtil {
|
|||
return true;
|
||||
}
|
||||
|
||||
private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
|
||||
public static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
|
||||
KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
|
||||
try {
|
||||
if (validateUsingKeySelector(signatureNode, sel)) {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package org.keycloak.saml.processing.core.saml.v2.util;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.bouncycastle.util.Arrays;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.common.util.DerUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
public class AssertionUtilTest {
|
||||
|
||||
private static final String PRIVATE_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==";
|
||||
|
||||
/**
|
||||
* The public certificate that corresponds to {@link #PRIVATE_KEY}.
|
||||
*/
|
||||
private static final String PUBLIC_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin";
|
||||
|
||||
@Test
|
||||
public void testSaml20Signed() throws Exception {
|
||||
|
||||
X509Certificate decodeCertificate = DerUtils.decodeCertificate(new ByteArrayInputStream(Base64.decode(PUBLIC_CERT)));
|
||||
|
||||
try (InputStream st = AssertionUtilTest.class.getResourceAsStream("saml20-signed-response.xml")) {
|
||||
Document document = DocumentUtil.getDocument(st);
|
||||
|
||||
Element assertion = DocumentUtil.getDirectChildElement(document.getDocumentElement(), "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion");
|
||||
|
||||
assertTrue(AssertionUtil.isSignatureValid(assertion, decodeCertificate.getPublicKey()));
|
||||
|
||||
// test manipulation of signature
|
||||
Element signatureElement = AssertionUtil.getSignature(assertion);
|
||||
byte[] validSignature = Base64.decode(signatureElement.getTextContent());
|
||||
|
||||
// change the signature value slightly
|
||||
byte[] invalidSignature = Arrays.clone(validSignature);
|
||||
invalidSignature[0] ^= invalidSignature[0];
|
||||
signatureElement.setTextContent(Base64.encodeBytes(invalidSignature));
|
||||
|
||||
// check that signature now is invalid
|
||||
assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
|
||||
|
||||
// restore valid signature, but remove Signature element, check that still invalid
|
||||
signatureElement.setTextContent(Base64.encodeBytes(validSignature));
|
||||
|
||||
assertion.removeChild(signatureElement);
|
||||
assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
51
server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
Executable file → Normal file
51
server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
Executable file → Normal file
|
@ -20,20 +20,55 @@ package org.keycloak.models;
|
|||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface ClientInitialAccessModel {
|
||||
public class ClientInitialAccessModel {
|
||||
|
||||
String getId();
|
||||
private String id;
|
||||
|
||||
RealmModel getRealm();
|
||||
private int timestamp;
|
||||
|
||||
int getTimestamp();
|
||||
private int expiration;
|
||||
|
||||
int getExpiration();
|
||||
private int count;
|
||||
|
||||
int getCount();
|
||||
private int remainingCount;
|
||||
|
||||
int getRemainingCount();
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
void decreaseRemainingCount();
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(int timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public void setExpiration(int expiration) {
|
||||
this.expiration = expiration;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public int getRemainingCount() {
|
||||
return remainingCount;
|
||||
}
|
||||
|
||||
public void setRemainingCount(int remainingCount) {
|
||||
this.remainingCount = remainingCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,4 +90,11 @@ public interface RealmProvider extends Provider {
|
|||
List<RealmModel> getRealms();
|
||||
boolean removeRealm(String id);
|
||||
void close();
|
||||
|
||||
ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
|
||||
ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
|
||||
void removeClientInitialAccessModel(RealmModel realm, String id);
|
||||
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
|
||||
void removeExpiredClientInitialAccess();
|
||||
void decreaseRemainingCount(RealmModel realm, ClientInitialAccessModel clientInitialAccess); // Separate provider method to ensure we decrease remainingCount atomically instead of doing classic update
|
||||
}
|
||||
|
|
|
@ -72,11 +72,6 @@ public interface UserSessionProvider extends Provider {
|
|||
/** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
|
||||
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
|
||||
|
||||
ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
|
||||
ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
|
||||
void removeClientInitialAccessModel(RealmModel realm, String id);
|
||||
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
|
||||
|
||||
void close();
|
||||
|
||||
}
|
||||
|
|
|
@ -372,13 +372,13 @@ public class SAMLEndpoint {
|
|||
assertionElement = DocumentUtil.getElement(holder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
|
||||
}
|
||||
|
||||
if (config.isWantAssertionsSigned() && config.isValidateSignature()) {
|
||||
if (!AssertionUtil.isSignatureValid(assertionElement, getIDPKeyLocator())) {
|
||||
logger.error("validation failed");
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
boolean signed = AssertionUtil.isSignedElement(assertionElement);
|
||||
if ((config.isWantAssertionsSigned() && !signed)
|
||||
|| (signed && config.isValidateSignature() && !AssertionUtil.isSignatureValid(assertionElement, getIDPKeyLocator()))) {
|
||||
logger.error("validation failed");
|
||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||
event.error(Errors.INVALID_SIGNATURE);
|
||||
return ErrorPage.error(session, Messages.INVALID_REQUESTER);
|
||||
}
|
||||
|
||||
AssertionType assertion = responseType.getAssertions().get(0).getAssertion();
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.ClientInitialAccessModel;
|
|||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
@ -65,7 +66,8 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
}
|
||||
|
||||
try {
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
|
||||
|
||||
ClientRegistrationPolicyManager.triggerAfterRegister(context, registrationAuth, clientModel);
|
||||
|
||||
|
@ -78,7 +80,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
|||
|
||||
if (auth.isInitialAccessToken()) {
|
||||
ClientInitialAccessModel initialAccessModel = auth.getInitialAccessModel();
|
||||
initialAccessModel.decreaseRemainingCount();
|
||||
session.realms().decreaseRemainingCount(realm, initialAccessModel);
|
||||
}
|
||||
|
||||
event.client(client.getClientId()).success();
|
||||
|
|
|
@ -88,7 +88,7 @@ public class ClientRegistrationAuth {
|
|||
jwt = tokenVerification.getJwt();
|
||||
|
||||
if (isInitialAccessToken()) {
|
||||
initialAccessModel = session.sessions().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
|
||||
initialAccessModel = session.realms().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
|
||||
if (initialAccessModel == null) {
|
||||
throw unauthorized("Initial Access Token not found");
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.keycloak.services.managers.ApplianceBootstrap;
|
|||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.UserStorageSyncManager;
|
||||
import org.keycloak.services.resources.admin.AdminRoot;
|
||||
import org.keycloak.services.scheduled.ClearExpiredClientInitialAccessTokens;
|
||||
import org.keycloak.services.scheduled.ClearExpiredEvents;
|
||||
import org.keycloak.services.scheduled.ClearExpiredUserSessions;
|
||||
import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
|
||||
|
@ -331,6 +332,7 @@ public class KeycloakApplication extends Application {
|
|||
try {
|
||||
TimerProvider timer = session.getProvider(TimerProvider.class);
|
||||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents");
|
||||
timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens");
|
||||
timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
|
||||
new UserStorageSyncManager().bootstrapPeriodic(sessionFactory, timer);
|
||||
} finally {
|
||||
|
|
|
@ -81,7 +81,7 @@ public class ClientInitialAccessResource {
|
|||
int expiration = config.getExpiration() != null ? config.getExpiration() : 0;
|
||||
int count = config.getCount() != null ? config.getCount() : 1;
|
||||
|
||||
ClientInitialAccessModel clientInitialAccessModel = session.sessions().createClientInitialAccessModel(realm, expiration, count);
|
||||
ClientInitialAccessModel clientInitialAccessModel = session.realms().createClientInitialAccessModel(realm, expiration, count);
|
||||
|
||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success();
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class ClientInitialAccessResource {
|
|||
public List<ClientInitialAccessPresentation> list() {
|
||||
auth.clients().requireView();
|
||||
|
||||
List<ClientInitialAccessModel> models = session.sessions().listClientInitialAccess(realm);
|
||||
List<ClientInitialAccessModel> models = session.realms().listClientInitialAccess(realm);
|
||||
List<ClientInitialAccessPresentation> reps = new LinkedList<>();
|
||||
for (ClientInitialAccessModel m : models) {
|
||||
ClientInitialAccessPresentation r = wrap(m);
|
||||
|
@ -115,7 +115,7 @@ public class ClientInitialAccessResource {
|
|||
public void delete(final @PathParam("id") String id) {
|
||||
auth.clients().requireManage();
|
||||
|
||||
session.sessions().removeClientInitialAccessModel(realm, id);
|
||||
session.realms().removeClientInitialAccessModel(realm, id);
|
||||
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright 2017 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.services.scheduled;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.timer.ScheduledTask;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClearExpiredClientInitialAccessTokens implements ScheduledTask {
|
||||
|
||||
@Override
|
||||
public void run(KeycloakSession session) {
|
||||
session.realms().removeExpiredClientInitialAccess();
|
||||
}
|
||||
}
|
|
@ -45,7 +45,23 @@ and adapter are all in the same JVM and you can debug them easily. If it is not
|
|||
|
||||
and you will be able to attach remote debugger to the test. Unfortunately server and adapter are running in different JVMs, so this won't help to debug those.
|
||||
|
||||
TODO: Improve and add more info about Wildfly debugging...
|
||||
### JBoss auth server debugging
|
||||
|
||||
When tests are run on JBoss based container (WildFly/EAP) there is possibility to attach a debugger, by default on localhost:5005.
|
||||
|
||||
The server won't wait to attach the debugger. There are some properties what can change the default behaviour.
|
||||
|
||||
-Dauth.server.debug.port=$PORT
|
||||
-Dauth.server.debug.suspend=y
|
||||
|
||||
More info: http://javahowto.blogspot.cz/2010/09/java-agentlibjdwp-for-attaching.html
|
||||
|
||||
### JBoss app server debugging
|
||||
|
||||
Analogically, there is the same behaviour for JBoss based app server as for auth server. The default port is set to 5006. There are app server properties.
|
||||
|
||||
-Dapp.server.debug.port=$PORT
|
||||
-Dapp.server.debug.suspend=y
|
||||
|
||||
## Testsuite logging
|
||||
|
||||
|
|
|
@ -164,6 +164,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
|
||||
session.sessions().removeExpired(realm);
|
||||
session.authenticationSessions().removeExpired(realm);
|
||||
session.realms().removeExpiredClientInitialAccess();
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,14 +20,17 @@ package org.keycloak.testsuite.admin;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.ClientInitialAccessResource;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
@ -88,4 +91,40 @@ public class InitialAccessTokenResourceTest extends AbstractAdminTest {
|
|||
assertEquals(5, list.get(0).getCount() + list.get(1).getCount());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPeriodicExpiration() throws ClientRegistrationException, InterruptedException {
|
||||
ClientInitialAccessPresentation response1 = resource.create(new ClientInitialAccessCreatePresentation(1, 1));
|
||||
ClientInitialAccessPresentation response2 = resource.create(new ClientInitialAccessCreatePresentation(1000, 1));
|
||||
ClientInitialAccessPresentation response3 = resource.create(new ClientInitialAccessCreatePresentation(1000, 0));
|
||||
ClientInitialAccessPresentation response4 = resource.create(new ClientInitialAccessCreatePresentation(0, 1));
|
||||
|
||||
List<ClientInitialAccessPresentation> list = resource.list();
|
||||
assertEquals(4, list.size());
|
||||
|
||||
setTimeOffset(10);
|
||||
|
||||
testingClient.testing().removeExpired(REALM_NAME);
|
||||
|
||||
list = resource.list();
|
||||
assertEquals(2, list.size());
|
||||
|
||||
List<String> remainingIds = list.stream()
|
||||
.map(initialAccessPresentation -> initialAccessPresentation.getId())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Assert.assertNames(remainingIds, response2.getId(), response4.getId());
|
||||
|
||||
setTimeOffset(2000);
|
||||
|
||||
testingClient.testing().removeExpired(REALM_NAME);
|
||||
|
||||
list = resource.list();
|
||||
assertEquals(1, list.size());
|
||||
Assert.assertEquals(list.get(0).getId(), response4.getId());
|
||||
|
||||
// Cleanup
|
||||
realm.clientInitialAccess().delete(response4.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.AuthorizationResource;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
|
@ -120,7 +119,6 @@ public class ExportAuthorizationSettingsTest extends AbstractAuthorizationTest {
|
|||
|
||||
//KEYCLOAK-4983
|
||||
@Test
|
||||
@Ignore
|
||||
public void testRoleBasedPolicyWithMultipleRoles() {
|
||||
ClientResource clientResource = getClientResource();
|
||||
|
||||
|
@ -152,31 +150,17 @@ public class ExportAuthorizationSettingsTest extends AbstractAuthorizationTest {
|
|||
//export authorization settings
|
||||
ResourceServerRepresentation exportSettings = authorizationResource.exportSettings();
|
||||
|
||||
//delete test-resource-server client
|
||||
testRealmResource().clients().get(clientResource.toRepresentation().getId()).remove();
|
||||
|
||||
//clear cache
|
||||
testRealmResource().clearRealmCache();
|
||||
//workaround for the fact that clearing realm cache doesn't clear authz cache
|
||||
testingClient.testing("test").cache("authorization").clear();
|
||||
|
||||
//create new client
|
||||
ClientRepresentation client = ClientBuilder.create()
|
||||
.clientId(RESOURCE_SERVER_CLIENT_ID)
|
||||
.authorizationServicesEnabled(true)
|
||||
.serviceAccountsEnabled(true)
|
||||
.build();
|
||||
testRealmResource().clients().create(client).close();
|
||||
|
||||
//import exported settings
|
||||
AuthorizationResource authorization = testRealmResource().clients().get(getClientByClientId(RESOURCE_SERVER_CLIENT_ID).getId()).authorization();
|
||||
authorization.importSettings(exportSettings);
|
||||
|
||||
//check imported settings - TODO
|
||||
PolicyRepresentation result = authorization.policies().findByName("role-based-policy");
|
||||
Map<String, String> config1 = result.getConfig();
|
||||
ResourceServerRepresentation settings = authorization.getSettings();
|
||||
System.out.println("");
|
||||
boolean found = false;
|
||||
for (PolicyRepresentation p : exportSettings.getPolicies()) {
|
||||
if (p.getName().equals("role-based-policy")) {
|
||||
found = true;
|
||||
Assert.assertTrue(p.getConfig().get("roles").contains("test-client-1/client-role") &&
|
||||
p.getConfig().get("roles").contains("test-client-2/client-role"));
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
Assert.fail("Policy \"role-based-policy\" was not found in exported settings.");
|
||||
}
|
||||
}
|
||||
|
||||
private ClientRepresentation getClientByClientId(String clientId) {
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package org.keycloak.testsuite.broker;
|
||||
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.SuiteContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
|
||||
|
||||
public class KcSamlSignedDocumentOnlyBrokerTest extends KcSamlBrokerTest {
|
||||
|
||||
public static class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
|
||||
|
||||
@Override
|
||||
public RealmRepresentation createProviderRealm() {
|
||||
RealmRepresentation realm = super.createProviderRealm();
|
||||
|
||||
realm.setPublicKey(REALM_PUBLIC_KEY);
|
||||
realm.setPrivateKey(REALM_PRIVATE_KEY);
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmRepresentation createConsumerRealm() {
|
||||
RealmRepresentation realm = super.createConsumerRealm();
|
||||
|
||||
realm.setPublicKey(REALM_PUBLIC_KEY);
|
||||
realm.setPrivateKey(REALM_PRIVATE_KEY);
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
|
||||
List<ClientRepresentation> clientRepresentationList = super.createProviderClients(suiteContext);
|
||||
|
||||
for (ClientRepresentation client : clientRepresentationList) {
|
||||
client.setClientAuthenticatorType("client-secret");
|
||||
client.setSurrogateAuthRequired(false);
|
||||
|
||||
Map<String, String> attributes = client.getAttributes();
|
||||
if (attributes == null) {
|
||||
attributes = new HashMap<>();
|
||||
client.setAttributes(attributes);
|
||||
}
|
||||
|
||||
attributes.put("saml.assertion.signature", "false");
|
||||
attributes.put("saml.server.signature", "true");
|
||||
attributes.put("saml.client.signature", "true");
|
||||
attributes.put("saml.signature.algorithm", "RSA_SHA256");
|
||||
attributes.put("saml.signing.private.key", IDP_SAML_SIGN_KEY);
|
||||
attributes.put("saml.signing.certificate", IDP_SAML_SIGN_CERT);
|
||||
}
|
||||
|
||||
return clientRepresentationList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
|
||||
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
|
||||
|
||||
Map<String, String> config = result.getConfig();
|
||||
|
||||
config.put("validateSignature", "true");
|
||||
config.put("wantAssertionsSigned", "false");
|
||||
config.put("wantAuthnRequestsSigned", "true");
|
||||
config.put("signingCertificate", IDP_SAML_SIGN_CERT);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BrokerConfiguration getBrokerConfiguration() {
|
||||
return KcSamlSignedBrokerConfiguration.INSTANCE;
|
||||
}
|
||||
|
||||
}
|
|
@ -74,6 +74,7 @@
|
|||
${auth.server.feature}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
${auth.server.jboss.jvm.debug.args}
|
||||
${auth.server.memory.settings}
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
${adapter.test.props}
|
||||
</property>
|
||||
<property name="javaVmArguments">
|
||||
${app.server.jboss.jvm.debug.args}
|
||||
${app.server.memory.settings}
|
||||
-Djava.net.preferIPv4Stack=true
|
||||
</property>
|
||||
|
|
|
@ -52,6 +52,11 @@
|
|||
<app.server.startup.timeout>60</app.server.startup.timeout>
|
||||
<app.server.memory.settings>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</app.server.memory.settings>
|
||||
|
||||
<!--debug properties-->
|
||||
<app.server.debug.port>5006</app.server.debug.port>
|
||||
<app.server.debug.suspend>n</app.server.debug.suspend>
|
||||
<app.server.jboss.jvm.debug.args>-agentlib:jdwp=transport=dt_socket,server=y,suspend=${app.server.debug.suspend},address=${app.server.host}:${app.server.debug.port}</app.server.jboss.jvm.debug.args>
|
||||
|
||||
<app.server.ssl.required>false</app.server.ssl.required>
|
||||
|
||||
<app.server.reverse-proxy.port.offset>500</app.server.reverse-proxy.port.offset>
|
||||
|
@ -205,7 +210,8 @@
|
|||
|
||||
<app.server.startup.timeout>${app.server.startup.timeout}</app.server.startup.timeout>
|
||||
<app.server.memory.settings>${app.server.memory.settings}</app.server.memory.settings>
|
||||
|
||||
<app.server.jboss.jvm.debug.args>${app.server.jboss.jvm.debug.args}</app.server.jboss.jvm.debug.args>
|
||||
|
||||
<app.server.reverse-proxy.port.offset>${app.server.reverse-proxy.port.offset}</app.server.reverse-proxy.port.offset>
|
||||
|
||||
<app.server.1.port.offset>${app.server.1.port.offset}</app.server.1.port.offset>
|
||||
|
|
|
@ -64,6 +64,11 @@
|
|||
<auth.server.jboss.skip.unpack>${auth.server.undertow}</auth.server.jboss.skip.unpack>
|
||||
<auth.server.jboss.startup.timeout>300</auth.server.jboss.startup.timeout>
|
||||
|
||||
<!--debug properties-->
|
||||
<auth.server.debug.port>5005</auth.server.debug.port>
|
||||
<auth.server.debug.suspend>n</auth.server.debug.suspend>
|
||||
<auth.server.jboss.jvm.debug.args>-agentlib:jdwp=transport=dt_socket,server=y,suspend=${auth.server.debug.suspend},address=${auth.server.host}:${auth.server.debug.port}</auth.server.jboss.jvm.debug.args>
|
||||
|
||||
<auth.server.remote>false</auth.server.remote>
|
||||
<auth.server.profile/>
|
||||
<auth.server.feature/>
|
||||
|
@ -226,6 +231,7 @@
|
|||
<auth.server.config.property.name>${auth.server.config.property.name}</auth.server.config.property.name>
|
||||
<auth.server.config.property.value>${auth.server.config.property.value}</auth.server.config.property.value>
|
||||
<auth.server.adapter.impl.class>${auth.server.adapter.impl.class}</auth.server.adapter.impl.class>
|
||||
<auth.server.jboss.jvm.debug.args>${auth.server.jboss.jvm.debug.args}</auth.server.jboss.jvm.debug.args>
|
||||
|
||||
<auth.server.profile>${auth.server.profile}</auth.server.profile>
|
||||
<auth.server.feature>${auth.server.feature}</auth.server.feature>
|
||||
|
|
Loading…
Reference in a new issue