keycloak-aplcache

Details

diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
index 8a4a487..281089a 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
@@ -37,5 +37,7 @@ public interface AccountProvider extends Provider {
 
     AccountProvider setSessions(List<UserSessionModel> sessions);
 
+    AccountProvider setPasswordSet(boolean passwordSet);
+
     AccountProvider setFeatures(boolean social, boolean events, boolean passwordUpdateSupported);
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 552d59a..e7a632b 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -8,6 +8,7 @@ import org.keycloak.account.freemarker.model.AccountSocialBean;
 import org.keycloak.account.freemarker.model.FeaturesBean;
 import org.keycloak.account.freemarker.model.LogBean;
 import org.keycloak.account.freemarker.model.MessageBean;
+import org.keycloak.account.freemarker.model.PasswordBean;
 import org.keycloak.account.freemarker.model.ReferrerBean;
 import org.keycloak.account.freemarker.model.SessionsBean;
 import org.keycloak.account.freemarker.model.TotpBean;
@@ -50,6 +51,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     private boolean socialEnabled;
     private boolean eventsEnabled;
     private boolean passwordUpdateSupported;
+    private boolean passwordSet;
     private KeycloakSession session;
     private FreeMarkerUtil freeMarker;
 
@@ -133,6 +135,8 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             case SESSIONS:
                 attributes.put("sessions", new SessionsBean(realm, sessions));
                 break;
+            case PASSWORD:
+                attributes.put("password", new PasswordBean(passwordSet));
         }
 
         try {
@@ -146,6 +150,11 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         }
     }
 
+    public AccountProvider setPasswordSet(boolean passwordSet) {
+        this.passwordSet = passwordSet;
+        return this;
+    }
+
     @Override
     public AccountProvider setError(String message) {
         this.message = message;
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
index af845d1..e84470f 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountSocialBean.java
@@ -4,6 +4,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.services.resources.AccountService;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.social.SocialLoader;
 import org.keycloak.social.SocialProvider;
@@ -52,7 +53,7 @@ public class AccountSocialBean {
         }
 
         // Removing last social provider is not possible if you don't have other possibility to authenticate
-        this.removeLinkPossible = availableLinks > 1 || user.getFederationLink() != null;
+        this.removeLinkPossible = availableLinks > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(user);
     }
 
     private SocialLinkModel getSocialLink(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java
new file mode 100644
index 0000000..2e2c3bd
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/PasswordBean.java
@@ -0,0 +1,18 @@
+package org.keycloak.account.freemarker.model;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordBean {
+
+    private boolean passwordSet;
+
+    public PasswordBean(boolean passwordSet) {
+        this.passwordSet = passwordSet;
+    }
+
+    public boolean isPasswordSet() {
+        return passwordSet;
+    }
+
+}
diff --git a/forms/common-themes/src/main/resources/theme/account/base/password.ftl b/forms/common-themes/src/main/resources/theme/account/base/password.ftl
index e5d5531..ccf7d0f 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/password.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/password.ftl
@@ -11,15 +11,17 @@
     </div>
 
     <form action="${url.passwordUrl}" class="form-horizontal" method="post">
-        <div class="form-group">
-            <div class="col-sm-2 col-md-2">
-                <label for="password" class="control-label">${rb.password}</label>
-            </div>
+        <#if password.passwordSet>
+            <div class="form-group">
+                <div class="col-sm-2 col-md-2">
+                    <label for="password" class="control-label">${rb.password}</label>
+                </div>
 
-            <div class="col-sm-10 col-md-10">
-                <input type="password" class="form-control" id="password" name="password" autofocus>
+                <div class="col-sm-10 col-md-10">
+                    <input type="password" class="form-control" id="password" name="password" autofocus>
+                </div>
             </div>
-        </div>
+        </#if>
 
         <div class="form-group">
             <div class="col-sm-2 col-md-2">
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
index 5cc6390..0f46861 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/applications.js
@@ -317,6 +317,7 @@ module.controller('ApplicationDetailCtrl', function($scope, realm, application, 
                 }, $scope.application, function() {
                     $scope.changed = false;
                     application = angular.copy($scope.application);
+                    $location.url("/realms/" + realm.realm + "/applications/" + application.name);
                     Notifications.success("Your changes have been saved to the application.");
                 });
             }
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
index b37d228..63e6089 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/oauth-clients.js
@@ -152,6 +152,7 @@ module.controller('OAuthClientDetailCtrl', function($scope, realm, oauth, OAuthC
                 }, $scope.oauth, function() {
                     $scope.changed = false;
                     oauth = angular.copy($scope.oauth);
+                    $location.url("/realms/" + realm.realm + "/oauth-clients/" + oauth.name);
                     Notifications.success("Your changes have been saved to the oauth client.");
                 });
             }
@@ -279,7 +280,10 @@ module.controller('OAuthClientInstallationCtrl', function($scope, realm, install
     $scope.realm = realm;
     $scope.oauth = oauth;
     $scope.installation = installation;
-    $scope.download = OAuthClientInstallation.url({ realm: $routeParams.realm, oauth: $routeParams.oauth });
+
+    $scope.download = function() {
+        saveAs(new Blob([$scope.installation], { type: $scope.type }), 'keycloak.json');
+    }
 });
 
 module.controller('OAuthClientRevocationCtrl', function($scope, realm, oauth, OAuthClient, $location, Dialog, Notifications) {
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 969aa9a..f7d6fba 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
@@ -1110,7 +1110,7 @@ module.controller('RealmBruteForceCtrl', function($scope, Realm, realm, $http, $
 
         $scope.changed = false;
         Realm.update(realmCopy, function () {
-            $location.url("/realms/" + realm.realm + "/sessions/brute-force");
+            $location.url("/realms/" + realm.realm + "/defense/brute-force");
             Notifications.success("Your changes have been saved to the realm.");
         });
     };
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-installation.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-installation.html
index 2ea3116..056ba24 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-installation.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/oauth-client-installation.html
@@ -26,7 +26,7 @@
         </form>
 
         <div class="pull-right form-actions">
-            <a class="btn btn-primary btn-lg" href="{{download}}" download="keycloak.json" type="submit">Download</a>
+            <a class="btn btn-primary btn-lg" data-ng-click="download()" type="submit">Download</a>
         </div>
 
     </div>
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
index 58fecaf..50e3ccc 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/ApplicationAdapter.java
@@ -53,6 +53,7 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
     public void setName(String name) {
         getDelegateForUpdate();
         updated.setName(name);
+        cacheSession.registerRealmInvalidation(cachedRealm.getId());
     }
 
     @Override
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/OAuthClientAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/OAuthClientAdapter.java
index 50708b9..1fe5b25 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/OAuthClientAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/OAuthClientAdapter.java
@@ -36,6 +36,7 @@ public class OAuthClientAdapter extends ClientAdapter implements OAuthClientMode
     public void setClientId(String id) {
         getDelegateForUpdate();
         updated.setClientId(id);
+        cacheSession.registerRealmInvalidation(cachedRealm.getId());
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 0900be8..78b3961 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -43,6 +43,7 @@ import org.keycloak.models.ModelReadOnlyException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.ModelToRepresentation;
@@ -264,6 +265,10 @@ public class AccountService {
     @Path("password")
     @GET
     public Response passwordPage() {
+        if (auth != null) {
+            account.setPasswordSet(isPasswordSet(auth.getUser()));
+        }
+
         return forwardToPage("password", AccountPages.PASSWORD);
     }
 
@@ -491,27 +496,34 @@ public class AccountService {
 
         UserModel user = auth.getUser();
 
+        boolean requireCurrent = isPasswordSet(user);
+        account.setPasswordSet(requireCurrent);
+
         String password = formData.getFirst("password");
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
+        if (requireCurrent) {
+            if (Validation.isEmpty(password)) {
+                setReferrerOnPage();
+                return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
+            }
+
+            UserCredentialModel cred = UserCredentialModel.password(password);
+            if (!session.users().validCredentials(realm, user, cred)) {
+                setReferrerOnPage();
+                return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
+            }
+        }
+
         if (Validation.isEmpty(passwordNew)) {
             setReferrerOnPage();
             return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
-        } else if (!passwordNew.equals(passwordConfirm)) {
-            setReferrerOnPage();
-            return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
         }
 
-        UserCredentialModel cred = UserCredentialModel.password(password);
-        if (Validation.isEmpty(password)) {
+        if (!passwordNew.equals(passwordConfirm)) {
             setReferrerOnPage();
-            return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
-        } else {
-            if (!session.users().validCredentials(realm, user, cred)) {
-                setReferrerOnPage();
-                return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
-            }
+            return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
         }
 
         try {
@@ -528,7 +540,7 @@ public class AccountService {
         event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
 
         setReferrerOnPage();
-        return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
+        return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
     }
 
     @Path("social-update")
@@ -583,7 +595,7 @@ public class AccountService {
                 if (link != null) {
 
                     // Removing last social provider is not possible if you don't have other possibility to authenticate
-                    if (session.users().getSocialLinks(user, realm).size() > 1 || user.getFederationLink() != null) {
+                    if (session.users().getSocialLinks(user, realm).size() > 1 || user.getFederationLink() != null || isPasswordSet(user)) {
                         session.users().removeSocialLink(realm, user, providerId);
 
                         logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername());
@@ -681,6 +693,21 @@ public class AccountService {
         return oauth.redirect(uriInfo, accountUri.toString());
     }
 
+    public static boolean isPasswordSet(UserModel user) {
+        boolean passwordSet = false;
+
+        if (user.getFederationLink() != null) {
+            passwordSet = true;
+        }
+
+        for (UserCredentialValueModel c : user.getCredentialsDirectly()) {
+            if (c.getType().equals(CredentialRepresentation.PASSWORD)) {
+                passwordSet = true;
+            }
+        }
+        return passwordSet;
+    }
+
     private String[] getReferrer() {
         String referrer = uriInfo.getQueryParameters().getFirst("referrer");
         if (referrer == null) {
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 c8a17be..83bc12c 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
@@ -159,13 +159,6 @@ public class AccountTest {
         });
     }
 
-    @Ignore
-    @Test
-    public void forever() throws Exception{
-        while (true) Thread.sleep(5000);
-    }
-
-
     @Test
     public void returnToAppFromQueryParam() {
         driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");