keycloak-aplcache

Changes

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/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 861bcca..a82e9e8 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -15,9 +15,9 @@
         {
             "username" : "bburke@redhat.com",
             "enabled": true,
-            "attributes" : {
-                "email" : "bburke@redhat.com"
-            },
+            "email" : "bburke@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
             "credentials" : [
                 { "type" : "password",
                     "value" : "password" }
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/JpaKeycloakSession.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
index 7812315..057679e 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaKeycloakSession.java
@@ -83,7 +83,9 @@ public class JpaKeycloakSession implements KeycloakSession {
             adapter.removeApplication(a.getId());
         }
 
-        em.createQuery("delete from " + OAuthClientEntity.class.getSimpleName() + " where realm = :realm").setParameter("realm", realm).executeUpdate();
+        for (OAuthClientModel oauth : adapter.getOAuthClients()) {
+            adapter.removeOAuthClient(oauth.getId());
+        }
 
         for (UserEntity u : em.createQuery("from UserEntity", UserEntity.class).getResultList()) {
             adapter.removeUser(u.getLoginName());
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/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index ae83661..c48e134 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -29,6 +29,16 @@ import java.util.Set;
 public class ImportTest extends AbstractModelTest {
 
     @Test
+    public void demoDelete() throws Exception {
+        // was having trouble deleting this realm from admin console
+        RealmRepresentation rep = AbstractModelTest.loadJson("testrealm2.json");
+        RealmModel realm = realmManager.importRealm(rep);
+        commit();
+        realm = realmManager.getRealmByName("demo-delete");
+        realmManager.removeRealm(realm);
+    }
+
+    @Test
     public void install() throws Exception {
         RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json");
         RealmModel realm = realmManager.createRealm("demo", rep.getRealm());
@@ -160,6 +170,13 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertTrue(realm.removeSocialLink(socialUser, "facebook"));
         Assert.assertNull(realm.getSocialLink(socialUser, "facebook"));
         Assert.assertFalse(realm.removeSocialLink(socialUser, "facebook"));
+
+        commit();
+
+        realm = realmManager.getRealm("demo");
+        realmManager.removeRealm(realm);
+
+
     }
 
     @Test
diff --git a/model/tests/src/test/resources/testrealm2.json b/model/tests/src/test/resources/testrealm2.json
new file mode 100755
index 0000000..ff9e3bc
--- /dev/null
+++ b/model/tests/src/test/resources/testrealm2.json
@@ -0,0 +1,100 @@
+{
+    "realm": "demo-delete",
+    "enabled": true,
+    "accessTokenLifespan": 3000,
+    "accessCodeLifespan": 10,
+    "accessCodeLifespanUserAction": 6000,
+    "sslNotRequired": true,
+    "registrationAllowed": false,
+    "social": false,
+    "updateProfileOnInitialSocialLogin": false,
+    "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
+    "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "requiredCredentials": [ "password" ],
+    "users" : [
+        {
+            "username" : "bburke@redhat.com",
+            "enabled": true,
+            "email" : "bburke@redhat.com",
+            "firstName": "Bill",
+            "lastName": "Burke",
+            "credentials" : [
+                { "type" : "password",
+                    "value" : "password" }
+            ]
+        }
+    ],
+    "roles" : {
+        "realm" : [
+            {
+                "name": "user",
+                "description": "User privileges"
+            },
+            {
+                "name": "admin",
+                "description": "Administrator privileges"
+            }
+        ]
+    },
+    "roleMappings": [
+        {
+            "username": "bburke@redhat.com",
+            "roles": ["user"]
+        }
+    ],
+    "scopeMappings": [
+        {
+            "client": "third-party",
+            "roles": ["user"]
+        },
+        {
+            "client": "customer-portal",
+            "roles": ["user"]
+        },
+        {
+            "client": "product-portal",
+            "roles": ["user"]
+        }
+
+    ],
+    "applications": [
+        {
+            "name": "customer-portal",
+            "enabled": true,
+            "adminUrl": "http://localhost:8080/customer-portal",
+            "redirectUris": [
+                "http://localhost:8080/customer-portal/*"
+            ],
+            "secret": "password"
+        },
+        {
+            "name": "product-portal",
+            "enabled": true,
+            "adminUrl": "http://localhost:8080/product-portal",
+            "redirectUris": [
+                "http://localhost:8080/product-portal/*"
+            ],
+            "secret": "password"
+        }
+    ],
+    "oauthClients": [
+        {
+            "name": "third-party",
+            "enabled": true,
+            "redirectUris": [
+                "http://localhost:8080/oauth-client/*",
+                "http://localhost:8080/oauth-client-cdi/*"
+            ],
+            "secret": "password"
+        }
+    ],
+    "applicationRoleMappings": {
+        "account": [
+            {
+                "username": "bburke@redhat.com",
+                "roles": ["manage-account"]
+            }
+        ]
+    }
+
+}
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/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index face034..83cf3fb 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -423,6 +423,8 @@ public class RealmManager {
         UserModel user = newRealm.addUser(userRep.getUsername());
         user.setEnabled(userRep.isEnabled());
         user.setEmail(userRep.getEmail());
+        user.setFirstName(userRep.getFirstName());
+        user.setLastName(userRep.getLastName());
         if (userRep.getAttributes() != null) {
             for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
                 user.setAttribute(entry.getKey(), entry.getValue());
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");
         }