Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-06-23 09:57:35 -04:00
commit 3ee86fedc7
35 changed files with 691 additions and 507 deletions

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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)) {

View file

@ -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

View 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;
}
}

View file

@ -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
}

View file

@ -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();
}

View file

@ -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();

View file

@ -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();

View file

@ -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");
}

View file

@ -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 {

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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

View file

@ -164,6 +164,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
session.sessions().removeExpired(realm);
session.authenticationSessions().removeExpired(realm);
session.realms().removeExpiredClientInitialAccess();
return Response.ok().build();
}

View file

@ -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());
}
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>