Details
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
index 4a3ecf7..a7473b0 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/application-sessions.html
@@ -37,7 +37,7 @@
<tr>
<th class="kc-table-actions" colspan="3">
<div class="pull-right">
- <a class="btn btn-primary" ng-click="logoutAll()">Logout All</a>
+ <a class="btn btn-primary" ng-click="logoutAll()">Invalidate All Sessions</a>
<a class="btn btn-primary" ng-click="loadUsers()">Show Users</a>
</div>
</th>
@@ -52,7 +52,7 @@
<tr data-ng-repeat="(user, data) in users">
<td><a href="#/realms/{{realm.realm}}/users/{{user}}">{{user}}</a></td>
<td>{{data.whenLoggedIn | date:'medium'}}</td>
- <td><a ng-click="logoutUser(user)">logout</a> </td>
+ <td><a ng-click="logoutUser(user)">invalidate session</a> </td>
</tr>
</tbody>
</table>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-sessions.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-sessions.html
index 0011ad3..b702f25 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-sessions.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/user-sessions.html
@@ -33,7 +33,7 @@
<tr data-ng-repeat="(application, data) in stats">
<td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
<td>{{data.whenLoggedIn | date:'medium'}}</td>
- <td><a ng-click="logoutApplication(application)">logout</a> </td>
+ <td><a ng-click="logoutApplication(application)">invalidate session</a> </td>
</tr>
</tbody>
</table>
diff --git a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
index ef94ebe..35576bd 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/action/LogoutAction.java
@@ -7,11 +7,12 @@ package org.keycloak.representations.adapters.action;
public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
protected String user;
+ protected int notBefore;
public LogoutAction() {
}
- public LogoutAction(String id, int expiration, String resource, String user) {
+ public LogoutAction(String id, int expiration, String resource, String user, int notBefore) {
super(id, expiration, resource, LOGOUT);
this.user = user;
}
@@ -24,6 +25,14 @@ public class LogoutAction extends AdminAction {
this.user = user;
}
+ public int getNotBefore() {
+ return notBefore;
+ }
+
+ public void setNotBefore(int notBefore) {
+ this.notBefore = notBefore;
+ }
+
@Override
public boolean validate() {
return LOGOUT.equals(action);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 6cf53a6..370b313 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -105,6 +105,9 @@ public class PreAuthActionsHandler {
userSessionManagement.logout(user);
} else {
log.info("logout of all sessions");
+ if (action.getNotBefore() > deployment.getNotBefore()) {
+ deployment.setNotBefore(action.getNotBefore());
+ }
userSessionManagement.logoutAll();
}
} catch (Exception e) {
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 2b2a0cf..95d6613 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -55,6 +55,9 @@ public interface UserModel {
void setTotp(boolean totp);
+ int getNotBefore();
+ void setNotBefore(int notBefore);
+
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 51daf7b..ab25a64 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -46,6 +46,7 @@ public class UserEntity {
protected boolean enabled;
protected boolean totp;
protected boolean emailVerified;
+ protected int notBefore;
@ManyToOne
protected RealmEntity realm;
@@ -158,4 +159,12 @@ public class UserEntity {
public void setCredentials(Collection<CredentialEntity> credentials) {
this.credentials = credentials;
}
+
+ public int getNotBefore() {
+ return notBefore;
+ }
+
+ public void setNotBefore(int notBefore) {
+ this.notBefore = notBefore;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index cff1659..4eec0ca 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -144,4 +144,14 @@ public class UserAdapter implements UserModel {
public void setTotp(boolean totp) {
user.setTotp(totp);
}
+
+ @Override
+ public int getNotBefore() {
+ return user.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ user.setNotBefore(notBefore);
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index abcdffc..5d3ed09 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -46,6 +46,16 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
}
@Override
+ public int getNotBefore() {
+ return user.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ user.setNotBefore(notBefore);
+ }
+
+ @Override
public String getFirstName() {
return user.getFirstName();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
index 753ab8b..a2f8692 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
@@ -23,6 +23,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
private boolean emailVerified;
private boolean totp;
private boolean enabled;
+ private int notBefore;
private String realmId;
@@ -97,6 +98,15 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
}
@MongoField
+ public int getNotBefore() {
+ return notBefore;
+ }
+
+ public void setNotBefore(int notBefore) {
+ this.notBefore = notBefore;
+ }
+
+ @MongoField
public String getRealmId() {
return realmId;
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 4d006d9..b1a473a 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -156,12 +156,19 @@ public class AuthenticationManager {
}
UserModel user = realm.getUserById(token.getSubject());
- if (user == null || !user.isEnabled()) {
+ if (user == null || !user.isEnabled() ) {
logger.info("Unknown user in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
+ if (token.getIssuedAt() < user.getNotBefore()) {
+ logger.info("Stale cookie");
+ expireIdentityCookie(realm, uriInfo);
+ return null;
+
+ }
+
return user;
} catch (VerificationException e) {
logger.info("Failed to verify identity cookie", e);
diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
index 96a49f0..3c0e741 100755
--- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java
@@ -108,16 +108,17 @@ public class ResourceAdminManager {
}
- public void logoutUser(RealmModel realm, String user) {
+ public void logoutUser(RealmModel realm, UserModel user) {
ResteasyClient client = new ResteasyClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default
.build();
try {
+ // don't set user notBefore as we don't want a database hit on a user driven logout
List<ApplicationModel> resources = realm.getApplications();
logger.debug("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) {
- logoutApplication(realm, resource, user, client);
+ logoutApplication(realm, resource, user.getId(), client, 0);
}
} finally {
client.close();
@@ -129,10 +130,11 @@ public class ResourceAdminManager {
.build();
try {
+ realm.setNotBefore((int)(System.currentTimeMillis()/1000));
List<ApplicationModel> resources = realm.getApplications();
logger.debug("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) {
- logoutApplication(realm, resource, null, client);
+ logoutApplication(realm, resource, null, client, realm.getNotBefore());
}
} finally {
client.close();
@@ -145,7 +147,8 @@ public class ResourceAdminManager {
.build();
try {
- logoutApplication(realm, resource, user, client);
+ resource.setNotBefore((int)(System.currentTimeMillis()/1000));
+ logoutApplication(realm, resource, user, client, resource.getNotBefore());
} finally {
client.close();
}
@@ -153,10 +156,10 @@ public class ResourceAdminManager {
}
- protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
+ protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
String managementUrl = resource.getManagementUrl();
if (managementUrl != null) {
- LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user);
+ LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user, notBefore);
String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index e1ecc0b..beb916e 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -131,7 +131,7 @@ public class TokenManager {
}
- if (refreshToken.getIssuedAt() < client.getNotBefore()) {
+ if (refreshToken.getIssuedAt() < client.getNotBefore() || refreshToken.getIssuedAt() < user.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index e3f9bbe..8aac691 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -176,7 +176,9 @@ public class UsersResource {
if (user == null) {
throw new NotFoundException();
}
- new ResourceAdminManager().logoutUser(realm, user.getId());
+ // set notBefore so that user will be forced to log in.
+ user.setNotBefore((int)(System.currentTimeMillis()/1000));
+ new ResourceAdminManager().logoutUser(realm, user);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 1f1d882..b9b1673 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -580,7 +580,7 @@ public class TokenService {
logger.info("Logging out: {0}", user.getLoginName());
authManager.expireIdentityCookie(realm, uriInfo);
authManager.expireRememberMeCookie(realm, uriInfo);
- resourceAdminManager.logoutUser(realm, user.getId());
+ resourceAdminManager.logoutUser(realm, user);
} else {
logger.info("No user logged in for logout");
}