keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-tokens.html 21(+20 -1)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 5(+5 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 12(+12 -0)
model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 4(+3 -1)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java 8(+5 -3)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java 4(+3 -1)
model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java 3(+2 -1)
Details
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index f510922..609ef45 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -90,5 +90,9 @@
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
<addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
<addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROVIDER_MAPPING"/>
+
+ <addColumn tableName="REALM">
+ <column name="LOGIN_LIFESPAN" type="INT"/>
+ </addColumn>
</changeSet>
</databaseChangeLog>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index ae57f06..dce6df6 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -19,6 +19,7 @@ public class RealmRepresentation {
protected Integer ssoSessionMaxLifespan;
protected Integer accessCodeLifespan;
protected Integer accessCodeLifespanUserAction;
+ protected Integer accessCodeLifespanLogin;
protected Boolean enabled;
protected String sslRequired;
protected Boolean passwordCredentialGrantAllowed;
@@ -199,6 +200,14 @@ public class RealmRepresentation {
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
}
+ public Integer getAccessCodeLifespanLogin() {
+ return accessCodeLifespanLogin;
+ }
+
+ public void setAccessCodeLifespanLogin(Integer accessCodeLifespanLogin) {
+ this.accessCodeLifespanLogin = accessCodeLifespanLogin;
+ }
+
public List<String> getDefaultRoles() {
return defaultRoles;
}
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index 140cbbe..7c5090d 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -826,6 +826,12 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.accessCodeLifespan = TimeUnit.convert($scope.realm.accessCodeLifespan, from, to);
});
+ $scope.realm.accessCodeLifespanLoginUnit = TimeUnit.autoUnit(realm.accessCodeLifespanLogin);
+ $scope.realm.accessCodeLifespanLogin = TimeUnit.toUnit(realm.accessCodeLifespanLogin, $scope.realm.accessCodeLifespanLoginUnit);
+ $scope.$watch('realm.accessCodeLifespanLoginUnit', function(to, from) {
+ $scope.realm.accessCodeLifespanLogin = TimeUnit.convert($scope.realm.accessCodeLifespanLogin, from, to);
+ });
+
$scope.realm.accessCodeLifespanUserActionUnit = TimeUnit.autoUnit(realm.accessCodeLifespanUserAction);
$scope.realm.accessCodeLifespanUserAction = TimeUnit.toUnit(realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit);
$scope.$watch('realm.accessCodeLifespanUserActionUnit', function(to, from) {
@@ -848,12 +854,14 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
delete realmCopy["accessCodeLifespanUnit"];
delete realmCopy["ssoSessionIdleTimeoutUnit"];
delete realmCopy["accessCodeLifespanUserActionUnit"];
+ delete realmCopy["accessCodeLifespanLoginUnit"];
realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
realmCopy.ssoSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit)
realmCopy.ssoSessionMaxLifespan = TimeUnit.toSeconds($scope.realm.ssoSessionMaxLifespan, $scope.realm.ssoSessionMaxLifespanUnit)
realmCopy.accessCodeLifespan = TimeUnit.toSeconds($scope.realm.accessCodeLifespan, $scope.realm.accessCodeLifespanUnit)
realmCopy.accessCodeLifespanUserAction = TimeUnit.toSeconds($scope.realm.accessCodeLifespanUserAction, $scope.realm.accessCodeLifespanUserActionUnit)
+ realmCopy.accessCodeLifespanLogin = TimeUnit.toSeconds($scope.realm.accessCodeLifespanLogin, $scope.realm.accessCodeLifespanLoginUnit)
Realm.update(realmCopy, function () {
$route.reload();
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-tokens.html
index c322bf2..2f30127 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-tokens.html
@@ -93,6 +93,25 @@
<span tooltip-placement="right" tooltip="Max time an application or oauth client has to finish the access token protocol. This should normally be 1 minute." class="fa fa-info-circle"></span>
</div>
<div class="form-group input-select">
+ <label class="col-sm-2 control-label" for="accessCodeLifespanLogin" class="two-lines">Login lifespan</label>
+ <div class="col-sm-5">
+ <div class="row">
+ <div class="col-sm-4">
+ <input class="form-control" type="number" required min="1" max="31536000" data-ng-model="realm.accessCodeLifespanLogin" id="accessCodeLifespanLogin" name="accessCodeLifespanLogin">
+ </div>
+ <div class="col-sm-4 select-kc">
+ <select name="accessCodeLifespanLoginUnit" data-ng-model="realm.accessCodeLifespanLoginUnit">
+ <option data-ng-selected="!realm.accessCodeLifespanLoginUnit">Seconds</option>
+ <option>Minutes</option>
+ <option>Hours</option>
+ <option>Days</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ <span tooltip-placement="right" tooltip="Max time a user has to complete a login. This is recommended to be relatively long. 30 minutes or more." class="fa fa-info-circle"></span>
+ </div>
+ <div class="form-group input-select">
<label class="col-sm-2 control-label" for="accessCodeLifespanUserAction" class="two-lines">Login user action lifespan</label>
<div class="col-sm-5">
<div class="row">
@@ -109,7 +128,7 @@
</div>
</div>
</div>
- <span tooltip-placement="right" tooltip="Max time a user has to complete a login and perform actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more." class="fa fa-info-circle"></span>
+ <span tooltip-placement="right" tooltip="Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long. 5 minutes or more." class="fa fa-info-circle"></span>
</div>
</fieldset>
<div class="pull-right form-actions" data-ng-show="access.manageRealm">
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 47a97a8..17792e5 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -34,6 +34,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int accessTokenLifespan;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
+ private int accessCodeLifespanLogin;
private int notBefore;
private String publicKeyPem;
@@ -230,6 +231,13 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
}
+ public int getAccessCodeLifespanLogin() {
+ return accessCodeLifespanLogin;
+ }
+
+ public void setAccessCodeLifespanLogin(int accessCodeLifespanLogin) {
+ this.accessCodeLifespanLogin = accessCodeLifespanLogin;
+ }
public int getNotBefore() {
return notBefore;
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d002fd0..4212e3b 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -99,6 +99,10 @@ public interface RealmModel extends RoleContainerModel {
void setAccessCodeLifespanUserAction(int seconds);
+ int getAccessCodeLifespanLogin();
+
+ void setAccessCodeLifespanLogin(int seconds);
+
String getPublicKeyPem();
void setPublicKeyPem(String publicKeyPem);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index c90afb5..d0963df 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -114,6 +114,7 @@ public class ModelToRepresentation {
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
rep.setAccessCodeLifespan(realm.getAccessCodeLifespan());
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
+ rep.setAccessCodeLifespanLogin(realm.getAccessCodeLifespanLogin());
rep.setSmtpServer(realm.getSmtpConfig());
rep.setBrowserSecurityHeaders(realm.getBrowserSecurityHeaders());
rep.setAccountTheme(realm.getAccountTheme());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java b/model/api/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
new file mode 100644
index 0000000..e3e30dd
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/utils/RealmInfoUtil.java
@@ -0,0 +1,21 @@
+package org.keycloak.models.utils;
+
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmInfoUtil {
+
+ public static int getDettachedClientSessionLifespan(RealmModel realm) {
+ int lifespan = realm.getAccessCodeLifespanLogin();
+ if (realm.getAccessCodeLifespanUserAction() > lifespan) {
+ lifespan = realm.getAccessCodeLifespanUserAction();
+ }
+ if (realm.getAccessCodeLifespan() > lifespan) {
+ lifespan = realm.getAccessCodeLifespan();
+ }
+ return lifespan;
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index a986de5..0bfa82b 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -78,6 +78,10 @@ public class RepresentationToModel {
newRealm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
else newRealm.setAccessCodeLifespanUserAction(300);
+ if (rep.getAccessCodeLifespanLogin() != null)
+ newRealm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin());
+ else newRealm.setAccessCodeLifespanLogin(1800);
+
if (rep.getSslRequired() != null) newRealm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.isPasswordCredentialGrantAllowed() != null) newRealm.setPasswordCredentialGrantAllowed(rep.isPasswordCredentialGrantAllowed());
if (rep.isRegistrationAllowed() != null) newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
@@ -258,8 +262,8 @@ public class RepresentationToModel {
if (rep.isResetPasswordAllowed() != null) realm.setResetPasswordAllowed(rep.isResetPasswordAllowed());
if (rep.getSslRequired() != null) realm.setSslRequired(SslRequired.valueOf(rep.getSslRequired().toUpperCase()));
if (rep.getAccessCodeLifespan() != null) realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
- if (rep.getAccessCodeLifespanUserAction() != null)
- realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
+ if (rep.getAccessCodeLifespanUserAction() != null) realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction());
+ if (rep.getAccessCodeLifespanLogin() != null) realm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin());
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 12cb2c5..8089e4e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -53,6 +53,7 @@ public class CachedRealm {
private int accessTokenLifespan;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
+ private int accessCodeLifespanLogin;
private int notBefore;
private PasswordPolicy passwordPolicy;
@@ -111,6 +112,7 @@ public class CachedRealm {
accessTokenLifespan = model.getAccessTokenLifespan();
accessCodeLifespan = model.getAccessCodeLifespan();
accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
+ accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
notBefore = model.getNotBefore();
passwordPolicy = model.getPasswordPolicy();
@@ -266,6 +268,9 @@ public class CachedRealm {
public int getAccessCodeLifespanUserAction() {
return accessCodeLifespanUserAction;
}
+ public int getAccessCodeLifespanLogin() {
+ return accessCodeLifespanLogin;
+ }
public String getPublicKeyPem() {
return publicKeyPem;
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 9ade50f..57013cf 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -301,6 +301,18 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public int getAccessCodeLifespanLogin() {
+ if (updated != null) return updated.getAccessCodeLifespanLogin();
+ return cached.getAccessCodeLifespanLogin();
+ }
+
+ @Override
+ public void setAccessCodeLifespanLogin(int seconds) {
+ getDelegateForUpdate();
+ updated.setAccessCodeLifespanLogin(seconds);
+ }
+
+ @Override
public String getPublicKeyPem() {
if (updated != null) return updated.getPublicKeyPem();
return cached.getPublicKeyPem();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 258b88f..9a4358b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -68,6 +68,8 @@ public class RealmEntity {
protected int accessCodeLifespan;
@Column(name="USER_ACTION_LIFESPAN")
protected int accessCodeLifespanUserAction;
+ @Column(name="LOGIN_LIFESPAN")
+ protected int accessCodeLifespanLogin;
@Column(name="NOT_BEFORE")
protected int notBefore;
@@ -244,6 +246,13 @@ public class RealmEntity {
public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
}
+ public int getAccessCodeLifespanLogin() {
+ return accessCodeLifespanLogin;
+ }
+
+ public void setAccessCodeLifespanLogin(int accessCodeLifespanLogin) {
+ this.accessCodeLifespanLogin = accessCodeLifespanLogin;
+ }
public String getPublicKeyPem() {
return publicKeyPem;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index f056449..9c0e751 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -363,6 +363,17 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public int getAccessCodeLifespanLogin() {
+ return realm.getAccessCodeLifespanLogin();
+ }
+
+ @Override
+ public void setAccessCodeLifespanLogin(int accessCodeLifespanLogin) {
+ realm.setAccessCodeLifespanLogin(accessCodeLifespanLogin);
+ em.flush();
+ }
+
+ @Override
public String getPublicKeyPem() {
return realm.getPublicKeyPem();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index c149e93..a51f801 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -295,8 +295,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
updateRealm();
}
-
-
@Override
public int getAccessCodeLifespan() {
return realm.getAccessCodeLifespan();
@@ -320,6 +318,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public void setAccessCodeLifespanLogin(int accessCodeLifespanLogin) {
+ realm.setAccessCodeLifespanLogin(accessCodeLifespanLogin);
+ updateRealm();
+ }
+
+ @Override
+ public int getAccessCodeLifespanLogin() {
+ return realm.getAccessCodeLifespanLogin();
+ }
+
+ @Override
public String getPublicKeyPem() {
return realm.getPublicKeyPem();
}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 96e7f85..42dc3aa 100644
--- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -22,6 +22,7 @@ import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.util.Time;
import java.util.Collection;
@@ -201,6 +202,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void removeExpiredUserSessions(RealmModel realm) {
int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
+ int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
Map<String, String> map = new MapReduceTask(sessionCache)
.mappedWith(UserSessionMapper.create(realm.getId()).expired(expired, expiredRefresh).emitKey())
@@ -212,7 +214,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
map = new MapReduceTask(sessionCache)
- .mappedWith(ClientSessionMapper.create(realm.getId()).expiredRefresh(expiredRefresh).requireNullUserSession(true).emitKey())
+ .mappedWith(ClientSessionMapper.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession(true).emitKey())
.reducedWith(new FirstResultReducer())
.execute();
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index 169a92d..dcae8e2 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -12,6 +12,7 @@ import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.util.Time;
import javax.persistence.EntityManager;
@@ -190,18 +191,19 @@ public class JpaUserSessionProvider implements UserSessionProvider {
public void removeExpiredUserSessions(RealmModel realm) {
int maxTime = Time.currentTime() - realm.getSsoSessionMaxLifespan();
int idleTime = Time.currentTime() - realm.getSsoSessionIdleTimeout();
+ int dettachedClientSessionExpired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
em.createNamedQuery("removeDetachedClientSessionRoleByExpired")
.setParameter("realmId", realm.getId())
- .setParameter("maxTime", idleTime)
+ .setParameter("maxTime", dettachedClientSessionExpired)
.executeUpdate();
em.createNamedQuery("removeDetachedClientSessionNoteByExpired")
.setParameter("realmId", realm.getId())
- .setParameter("maxTime", idleTime)
+ .setParameter("maxTime", dettachedClientSessionExpired)
.executeUpdate();
em.createNamedQuery("removeDetachedClientSessionByExpired")
.setParameter("realmId", realm.getId())
- .setParameter("maxTime", idleTime)
+ .setParameter("maxTime", dettachedClientSessionExpired)
.executeUpdate();
em.createNamedQuery("removeClientSessionRoleByExpired")
.setParameter("realmId", realm.getId())
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index cd5eef1..41081c0 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -14,6 +14,7 @@ import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureEntity;
import org.keycloak.models.sessions.mem.entities.UsernameLoginFailureKey;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.util.Time;
import java.util.Collections;
@@ -191,10 +192,11 @@ public class MemUserSessionProvider implements UserSessionProvider {
}
}
}
+ int expired = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
Iterator<ClientSessionEntity> citr = clientSessions.values().iterator();
while (citr.hasNext()) {
ClientSessionEntity c = citr.next();
- if (c.getSession() == null && c.getRealmId().equals(realm.getId()) && c.getTimestamp() < Time.currentTime() - realm.getSsoSessionIdleTimeout()) {
+ if (c.getSession() == null && c.getRealmId().equals(realm.getId()) && c.getTimestamp() < expired) {
citr.remove();
}
}
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index 6ddebb9..6630855 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -17,6 +17,7 @@ import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUsernameLoginFailureEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.RealmInfoUtil;
import org.keycloak.util.Time;
import java.util.LinkedList;
@@ -190,7 +191,7 @@ public class MongoUserSessionProvider implements UserSessionProvider {
query = new QueryBuilder()
.and("sessionId").is(null)
.and("realmId").is(realm.getId())
- .and("timestamp").lessThan(currentTime - realm.getSsoSessionIdleTimeout())
+ .and("timestamp").lessThan(currentTime - RealmInfoUtil.getDettachedClientSessionLifespan(realm))
.get();
mongoStore.removeEntities(MongoClientSessionEntity.class, query, invocationContext);
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index 24861f3..9e52c97 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -91,7 +91,19 @@ public class ClientSessionCode {
return false;
}
- int lifespan = action.equals(ClientSessionModel.Action.CODE_TO_TOKEN) ? realm.getAccessCodeLifespan() : realm.getAccessCodeLifespanUserAction();
+ int lifespan;
+ switch (action) {
+ case CODE_TO_TOKEN:
+ lifespan = realm.getAccessCodeLifespan();
+ break;
+ case AUTHENTICATE:
+ lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
+ break;
+ default:
+ lifespan = realm.getAccessCodeLifespanUserAction();
+ break;
+ }
+
return timestamp + lifespan > Time.currentTime();
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index d5d4867..3590f71 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -272,7 +272,10 @@ public class LoginActionsService {
event.error(Errors.INVALID_CODE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
}
+
ClientSessionModel clientSession = clientCode.getClientSession();
+ event.detail(Details.CODE_ID, clientSession.getId());
+
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 722717f..504b536 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -157,11 +157,6 @@ public class AccountTest {
});
}
- @Test @Ignore
- public void runit() throws Exception {
- Thread.sleep(10000000);
- }
-
@Test
public void returnToAppFromQueryParam() {
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
@@ -224,7 +219,7 @@ public class AccountTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().session((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().session((String) null).error("invalid_user_credentials").assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "new-password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 6626b60..5dd37bc 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -63,6 +63,7 @@ public class RealmTest extends AbstractClientTest {
RealmRepresentation rep = realm.toRepresentation();
rep.setSsoSessionIdleTimeout(123);
rep.setSsoSessionMaxLifespan(12);
+ rep.setAccessCodeLifespanLogin(1234);
realm.update(rep);
@@ -70,6 +71,7 @@ public class RealmTest extends AbstractClientTest {
assertEquals(123, rep.getSsoSessionIdleTimeout().intValue());
assertEquals(12, rep.getSsoSessionMaxLifespan().intValue());
+ assertEquals(1234, rep.getAccessCodeLifespanLogin().intValue());
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index a1ef54c..75a83f4 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -116,7 +116,7 @@ public class LoginTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
}
@Test
@@ -136,7 +136,7 @@ public class LoginTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@@ -164,7 +164,7 @@ public class LoginTest {
Assert.assertEquals("Account is disabled, contact admin", loginPage.getError());
- events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@@ -184,7 +184,7 @@ public class LoginTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent();
}
@Test
@@ -199,6 +199,36 @@ public class LoginTest {
}
@Test
+ public void loginNoTimeoutWithLongWait() {
+ try {
+ loginPage.open();
+
+ Time.setOffset(1700);
+
+ loginPage.login("login-test", "password");
+
+ events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId();
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+ @Test
+ public void loginTimeout() {
+ try {
+ loginPage.open();
+
+ Time.setOffset(1850);
+
+ loginPage.login("login-test", "password");
+
+ events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId();
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+ @Test
public void loginLoginHint() {
String loginFormUrl = oauth.getLoginFormUrl() + "&login_hint=login-test";
driver.navigate().to(loginFormUrl);
@@ -274,7 +304,7 @@ public class LoginTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
- events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
+ events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent();
}
// KEYCLOAK-1037
@@ -288,7 +318,7 @@ public class LoginTest {
loginPage.assertCurrent();
Assert.assertEquals("Login timeout. Please login again", loginPage.getError());
- events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().assertEvent();
+ events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();
} finally {
Time.setOffset(0);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index 580ba2a..45795da 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -43,6 +43,7 @@ import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.Time;
import org.openqa.selenium.WebDriver;
import java.net.MalformedURLException;
@@ -113,7 +114,7 @@ public class LoginTotpTest {
loginPage.assertCurrent();
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
+ events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
}
@Test
@@ -139,45 +140,29 @@ public class LoginTotpTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
- events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
+ events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
}
@Test
public void loginWithTotpExpiredPasswordToken() throws Exception {
try {
-
loginPage.open();
loginPage.login("test-user@localhost", "password");
- keycloakRule.configure(new KeycloakSetup() {
- @Override
- public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- lifespan = appRealm.getAccessCodeLifespanUserAction();
- appRealm.setAccessCodeLifespanUserAction(1);
- }
- });
-
loginTotpPage.assertCurrent();
- Thread.sleep(2000);
+ Time.setOffset(350);
loginTotpPage.login(totp.generate("totpSecret"));
loginPage.assertCurrent();
- Assert.assertEquals("Login timeout. Please login again", loginPage.getError());
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
- AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("expired_code")
- .user((String)null)
- .clearDetails()
+ AssertEvents.ExpectedEvent expectedEvent = events.expectLogin().error("invalid_user_credentials")
.session((String) null);
expectedEvent.assertEvent();
} finally {
- keycloakRule.configure(new KeycloakSetup() {
- @Override
- public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
- appRealm.setAccessCodeLifespanUserAction(lifespan);
- }
- });
+ Time.setOffset(0);
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
index db5d370..228416d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java
@@ -294,6 +294,61 @@ public class UserSessionProviderTest {
}
@Test
+ public void testExpireDetachedClientSessions() {
+ try {
+ realm.setAccessCodeLifespan(10);
+ realm.setAccessCodeLifespanUserAction(10);
+ realm.setAccessCodeLifespanLogin(30);
+
+ // Login lifespan is largest
+ String clientSessionId = session.sessions().createClientSession(realm, realm.findClient("test-app")).getId();
+
+ Time.setOffset(25);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNotNull(session.sessions().getClientSession(clientSessionId));
+
+ Time.setOffset(35);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNull(session.sessions().getClientSession(clientSessionId));
+
+ // User action is largest
+ realm.setAccessCodeLifespanUserAction(40);
+
+ Time.setOffset(0);
+ clientSessionId = session.sessions().createClientSession(realm, realm.findClient("test-app")).getId();
+
+ Time.setOffset(35);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNotNull(session.sessions().getClientSession(clientSessionId));
+
+ Time.setOffset(45);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNull(session.sessions().getClientSession(clientSessionId));
+
+ // Access code is largest
+ realm.setAccessCodeLifespan(50);
+
+ Time.setOffset(0);
+ clientSessionId = session.sessions().createClientSession(realm, realm.findClient("test-app")).getId();
+
+ Time.setOffset(45);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNotNull(session.sessions().getClientSession(clientSessionId));
+
+ Time.setOffset(55);
+ session.sessions().removeExpiredUserSessions(realm);
+ assertNull(session.sessions().getClientSession(clientSessionId));
+ } finally {
+ Time.setOffset(0);
+
+ realm.setAccessCodeLifespan(60);
+ realm.setAccessCodeLifespanUserAction(300);
+ realm.setAccessCodeLifespanLogin(1800);
+
+ }
+ }
+
+ @Test
public void testGetByClient() {
UserSessionModel[] sessions = createSessions();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 7ecd292..8048ed0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -140,7 +140,7 @@ public class AuthorizationCodeTest {
assertEquals("access_denied", error);
events.expectLogin().error("rejected_by_user").user((String) null).session((String) null)
- .removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID)
+ .removeDetail(Details.USERNAME)
.detail(Details.REDIRECT_URI, "http://localhost:8081/auth/realms/test/protocol/openid-connect/oauth/oob")
.assertEvent().getDetails().get(Details.CODE_ID);