keycloak-aplcache

Details

diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java
index 7fa6b40..242225b 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/AccountBean.java
@@ -3,6 +3,8 @@ package org.keycloak.account.freemarker.model;
 import org.keycloak.models.UserModel;
 
 import javax.ws.rs.core.MultivaluedMap;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -11,10 +13,20 @@ public class AccountBean {
 
     private final UserModel user;
     private final MultivaluedMap<String, String> profileFormData;
+    private final Map<String, String> attributes = new HashMap<>();
 
     public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
         this.user = user;
         this.profileFormData = profileFormData;
+        attributes.putAll(user.getAttributes());
+        if (profileFormData != null) {
+            for (String key : profileFormData.keySet()) {
+                if (key.startsWith("user.attributes.")) {
+                    String attribute = key.substring("user.attributes.".length());
+                    attributes.put(attribute, profileFormData.getFirst(key));
+                }
+            }
+        }
     }
 
     public String getFirstName() {
@@ -33,4 +45,8 @@ public class AccountBean {
         return profileFormData != null ?  profileFormData.getFirst("email") :user.getEmail();
     }
 
+    public Map<String, String> getAttributes() {
+        return attributes;
+    }
+
 }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/account.ftl b/forms/common-themes/src/main/resources/theme/account/base/account.ftl
index 0b8d18e..89e73b8 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/account.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/account.ftl
@@ -56,6 +56,52 @@
         </div>
 
         <div class="form-group">
+            <div class="col-sm-2 col-md-2">
+                <label for="user.attributes.street" class="control-label">${rb.street}</label>
+            </div>
+
+            <div class="col-sm-10 col-md-10">
+                <input type="text" class="form-control" id="user.attributes.street" name="user.attributes.street" value="${(account.attributes.street!'')?html}"/>
+            </div>
+        </div>
+        <div class="form-group">
+            <div class="col-sm-2 col-md-2">
+                <label for="user.attributes.locality" class="control-label">${rb.locality}</label>
+            </div>
+
+            <div class="col-sm-10 col-md-10">
+                <input type="text" class="form-control" id="user.attributes.locality" name="user.attributes.locality" value="${(account.attributes.locality!'')?html}"/>
+            </div>
+        </div>
+        <div class="form-group">
+            <div class="col-sm-2 col-md-2">
+                <label for="user.attributes.region" class="control-label">${rb.region}</label>
+            </div>
+
+            <div class="col-sm-10 col-md-10">
+                <input type="text" class="form-control" id="user.attributes.region" name="user.attributes.region" value="${(account.attributes.region!'')?html}"/>
+            </div>
+        </div>
+        <div class="form-group">
+            <div class="col-sm-2 col-md-2">
+                <label for="user.attributes.postal_code" class="control-label">${rb.postal_code}</label>
+            </div>
+
+            <div class="col-sm-10 col-md-10">
+                <input type="text" class="form-control" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(account.attributes.postal_code!'')?html}"/>
+            </div>
+        </div>
+        <div class="form-group">
+            <div class="col-sm-2 col-md-2">
+                <label for="user.attributes.country" class="control-label">${rb.country}</label>
+            </div>
+
+            <div class="col-sm-10 col-md-10">
+                <input type="text" class="form-control" id="user.attributes.country" name="user.attributes.country" value="${(account.attributes.country!'')?html}"/>
+            </div>
+        </div>
+
+        <div class="form-group">
             <div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
                 <div class="">
                     <#if url.referrerURI??><a href="${url.referrerURI}">Back to application</a></#if>
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
index e49a938..9ada684 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties
@@ -8,6 +8,11 @@ passwordConfirm=Confirmation
 passwordNew=New Password
 successHeader=Success!
 username=Username
+street=Street
+locality=City or Locality
+region=State, Province, or Region
+postal_code=Zip or Postal code
+country=Country
 
 missingFirstName=Please specify first name
 invalidEmail=Invalid email address
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
index 4603153..3d50839 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js
@@ -179,12 +179,15 @@ module.controller('UserListCtrl', function($scope, realm, User) {
 
 module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, $location, Dialog, Notifications) {
     $scope.realm = realm;
-    $scope.user = angular.copy(user);
     $scope.create = !user.username;
 
     if ($scope.create) {
-        $scope.user.enabled = true;
+        $scope.user = { enabled: true, attributes: {} }
     } else {
+        if (!user.attributes) {
+            user.attributes = {}
+        }
+        $scope.user = angular.copy(user);
         if(user.federationLink) {
             console.log("federationLink is not null");
             UserFederationInstances.get({realm : realm.realm, instance: user.federationLink}, function(link) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-attribute-entry.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-attribute-entry.html
new file mode 100755
index 0000000..b090261
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-attribute-entry.html
@@ -0,0 +1,38 @@
+<fieldset>
+    <legend collapsed><span class="text">Contact Information</span>  <span tooltip-placement="right" tooltip="Expand this section to configure user's contact information." class="fa fa-info-circle"></span></legend>
+    <div class="form-group clearfix block">
+        <label class="col-sm-2 control-label" for="street">Street</label>
+        <div class="col-sm-6">
+            <input ng-model="user.attributes.street" class="form-control" type="text" name="street" id="street" />
+        </div>
+        <span tooltip-placement="right" tooltip="Street address." class="fa fa-info-circle"></span>
+    </div>
+    <div class="form-group clearfix block">
+        <label class="col-sm-2 control-label" for="locality">City or Locality</label>
+        <div class="col-sm-6">
+            <input ng-model="user.attributes.locality" class="form-control" type="text" name="locality" id="locality" />
+        </div>
+        <span tooltip-placement="right" tooltip="City or locality." class="fa fa-info-circle"></span>
+    </div>
+    <div class="form-group clearfix block">
+        <label class="col-sm-2 control-label" for="region">State, Province, or Region</label>
+        <div class="col-sm-6">
+            <input ng-model="user.attributes.region" class="form-control" type="text" name="region" id="region" />
+        </div>
+        <span tooltip-placement="right" tooltip="State, province, prefecture, or region." class="fa fa-info-circle"></span>
+    </div>
+    <div class="form-group clearfix block">
+        <label class="col-sm-2 control-label" for="postal_code">Zip or Postal code</label>
+        <div class="col-sm-6">
+            <input ng-model="user.attributes.postal_code" class="form-control" type="text" name="postal_code" id="postal_code" />
+        </div>
+        <span tooltip-placement="right" tooltip="Zip code or postal code." class="fa fa-info-circle"></span>
+    </div>
+    <div class="form-group clearfix block">
+        <label class="col-sm-2 control-label" for="country">Country</label>
+        <div class="col-sm-6">
+            <input ng-model="user.attributes.country" class="form-control" type="text" name="country" id="country" />
+        </div>
+        <span tooltip-placement="right" tooltip="Country name." class="fa fa-info-circle"></span>
+    </div>
+</fieldset>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
index 8ed77b7..59fa3b2 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
@@ -101,6 +101,7 @@
                     <span tooltip-placement="right" tooltip="Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address.  'Update profile' requires user to enter in new personal information.  'Update password' requires user to enter in a new password.  'Configure TOTP' requires setup of a mobile password generator." class="fa fa-info-circle"></span>
                 </div>
             </fieldset>
+            <div data-ng-include data-src="resourceUrl + '/partials/user-attribute-entry.html'"></div>
             <div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
                 <button kc-cancel data-ng-click="cancel()">Cancel</button>
                 <button kc-save data-ng-show="changed">Save</button>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
index 6899731..2f43473 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties
@@ -6,6 +6,11 @@ register=Register
 registerWith=Register with
 allRequired=All fields are required
 alreadyHaveAccount=Already have an account?
+street=Street
+locality=City or Locality
+region=State, Province, or Region
+postal_code=Zip or Postal code
+country=Country
 
 poweredByKeycloak=Powered by Keycloak
 
diff --git a/forms/common-themes/src/main/resources/theme/login/base/register.ftl b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
index 113351b..46ed92b 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/register.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
@@ -60,6 +60,53 @@
                 </div>
             </div>
 
+            <div class="form-group">
+                <div class="${properties.kcLabelWrapperClass!}">
+                    <label for="user.attributes.street" class="${properties.kcLabelClass!}">${rb.street}</label>
+                </div>
+
+                <div class="col-sm-10 col-md-10">
+                    <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.street" name="user.attributes.street"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <div class="${properties.kcLabelWrapperClass!}">
+                    <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${rb.locality}</label>
+                </div>
+
+                <div class="col-sm-10 col-md-10">
+                    <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.locality" name="user.attributes.locality"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <div class="${properties.kcLabelWrapperClass!}">
+                    <label for="user.attributes.region" class="${properties.kcLabelClass!}">${rb.region}</label>
+                </div>
+
+                <div class="col-sm-10 col-md-10">
+                    <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.region" name="user.attributes.region"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <div class="${properties.kcLabelWrapperClass!}">
+                    <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${rb.postal_code}</label>
+                </div>
+
+                <div class="col-sm-10 col-md-10">
+                    <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.postal_code" name="user.attributes.postal_code"/>
+                </div>
+            </div>
+            <div class="form-group">
+                <div class="${properties.kcLabelWrapperClass!}">
+                    <label for="user.attributes.country" class="${properties.kcLabelClass!}">${rb.country}</label>
+                </div>
+
+                <div class="col-sm-10 col-md-10">
+                    <input type="text" class="${properties.kcInputClass!}"  id="user.attributes.country" name="user.attributes.country"/>
+                </div>
+            </div>
+
+
             <div class="${properties.kcFormGroupClass!}">
                 <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
                     <div class="${properties.kcFormOptionsWrapperClass!}">
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 337f01d..d286a14 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -432,6 +432,8 @@ public class AccountService {
 
             user.setEmail(formData.getFirst("email"));
 
+            AttributeFormDataProcessor.process(formData, realm, user);
+
             event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
 
             if (emailChanged) {
diff --git a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
new file mode 100755
index 0000000..e426b10
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java
@@ -0,0 +1,28 @@
+package org.keycloak.services.resources;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AttributeFormDataProcessor {
+    /**
+     * Looks for "user.attributes." keys in the form data and sets the appropriate UserModel.attribute from it.
+     *
+     * @param formData
+     * @param realm
+     * @param user
+     */
+    public static void process(MultivaluedMap<String, String> formData, RealmModel realm, UserModel user) {
+        for (String key : formData.keySet()) {
+            if (!key.startsWith("user.attributes.")) continue;
+            String attribute = key.substring("user.attributes.".length());
+            user.setAttribute(attribute, formData.getFirst(key));
+        }
+
+    }
+}
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 8a3abec..f62bd5c 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -526,6 +526,8 @@ public class LoginActionsService {
             }
         }
 
+        AttributeFormDataProcessor.process(formData, realm, user);
+
         event.user(user).success();
         event.reset();
 
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 504b536..efe3fd4 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,6 +157,11 @@ 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");
@@ -219,7 +224,7 @@ public class AccountTest {
 
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
 
-        events.expectLogin().session((String) null).error("invalid_user_credentials").assertEvent();
+        events.expectLogin().session((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
 
         loginPage.open();
         loginPage.login("test-user@localhost", "new-password");