keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java 31(+31 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientInitialAccessAdapter.java 86(+0 -86)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 71(+0 -71)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientInitialAccessMapper.java 96(+0 -96)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientInitialAccessPredicate.java 76(+0 -76)
services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java 6(+4 -2)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java 6(+3 -3)
services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java 32(+32 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index 5fe725f..1d7ce64 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -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);
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 0476698..202a051 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -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;
- }
-
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 718d19a..5c53d45 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -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);
@@ -519,4 +524,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;
+ }
}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
index b3872b8..bd55645 100644
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -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>
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 5d3fa81..f23198c 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -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>
diff --git a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
old mode 100755
new mode 100644
index 149b6aa..24b5546
--- a/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/ClientInitialAccessModel.java
@@ -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;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
index 4e6070f..f3a26f1 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmProvider.java
@@ -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
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index d474e89..848c098 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -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();
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
index 2974b6c..da693aa 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -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();
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
index 8382155..9b2b284 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
@@ -85,7 +85,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");
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
index dfd28cc..0fe0090 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java
@@ -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.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.requireManage();
- session.sessions().removeClientInitialAccessModel(realm, id);
+ session.realms().removeClientInitialAccessModel(realm, id);
adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index c16bb6a..42a2fd8 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -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 {
diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java
new file mode 100644
index 0000000..94a4f6b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredClientInitialAccessTokens.java
@@ -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();
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index b868827..f18bbbd 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -164,6 +164,8 @@ public class TestingResourceProvider implements RealmResourceProvider {
session.sessions().removeExpired(realm);
session.authenticationSessions().removeExpired(realm);
+ session.realms().removeExpiredClientInitialAccess();
+
return Response.ok().build();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
index 055bfa2..064e7b8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/InitialAccessTokenResourceTest.java
@@ -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());
+ }
+
}