Merge pull request #1018 from patriot1burke/master
reg/account custom attributes
This commit is contained in:
commit
94c778f22f
12 changed files with 201 additions and 3 deletions
|
@ -3,6 +3,8 @@ package org.keycloak.account.freemarker.model;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -11,10 +13,20 @@ public class AccountBean {
|
||||||
|
|
||||||
private final UserModel user;
|
private final UserModel user;
|
||||||
private final MultivaluedMap<String, String> profileFormData;
|
private final MultivaluedMap<String, String> profileFormData;
|
||||||
|
private final Map<String, String> attributes = new HashMap<>();
|
||||||
|
|
||||||
public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
|
public AccountBean(UserModel user, MultivaluedMap<String, String> profileFormData) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.profileFormData = profileFormData;
|
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() {
|
public String getFirstName() {
|
||||||
|
@ -33,4 +45,8 @@ public class AccountBean {
|
||||||
return profileFormData != null ? profileFormData.getFirst("email") :user.getEmail();
|
return profileFormData != null ? profileFormData.getFirst("email") :user.getEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,52 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 class="form-group">
|
||||||
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|
|
@ -8,6 +8,11 @@ passwordConfirm=Confirmation
|
||||||
passwordNew=New Password
|
passwordNew=New Password
|
||||||
successHeader=Success!
|
successHeader=Success!
|
||||||
username=Username
|
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
|
missingFirstName=Please specify first name
|
||||||
invalidEmail=Invalid email address
|
invalidEmail=Invalid email address
|
||||||
|
|
|
@ -179,12 +179,15 @@ module.controller('UserListCtrl', function($scope, realm, User) {
|
||||||
|
|
||||||
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, $location, Dialog, Notifications) {
|
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, $location, Dialog, Notifications) {
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.user = angular.copy(user);
|
|
||||||
$scope.create = !user.username;
|
$scope.create = !user.username;
|
||||||
|
|
||||||
if ($scope.create) {
|
if ($scope.create) {
|
||||||
$scope.user.enabled = true;
|
$scope.user = { enabled: true, attributes: {} }
|
||||||
} else {
|
} else {
|
||||||
|
if (!user.attributes) {
|
||||||
|
user.attributes = {}
|
||||||
|
}
|
||||||
|
$scope.user = angular.copy(user);
|
||||||
if(user.federationLink) {
|
if(user.federationLink) {
|
||||||
console.log("federationLink is not null");
|
console.log("federationLink is not null");
|
||||||
UserFederationInstances.get({realm : realm.realm, instance: user.federationLink}, function(link) {
|
UserFederationInstances.get({realm : realm.realm, instance: user.federationLink}, function(link) {
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
<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>
|
</div>
|
||||||
</fieldset>
|
</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">
|
<div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
|
||||||
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
<button kc-cancel data-ng-click="cancel()">Cancel</button>
|
||||||
<button kc-save data-ng-show="changed">Save</button>
|
<button kc-save data-ng-show="changed">Save</button>
|
||||||
|
|
|
@ -6,6 +6,11 @@ register=Register
|
||||||
registerWith=Register with
|
registerWith=Register with
|
||||||
allRequired=All fields are required
|
allRequired=All fields are required
|
||||||
alreadyHaveAccount=Already have an account?
|
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
|
poweredByKeycloak=Powered by Keycloak
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,53 @@
|
||||||
</div>
|
</div>
|
||||||
</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 class="${properties.kcFormGroupClass!}">
|
||||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||||
|
|
|
@ -432,6 +432,8 @@ public class AccountService {
|
||||||
|
|
||||||
user.setEmail(formData.getFirst("email"));
|
user.setEmail(formData.getFirst("email"));
|
||||||
|
|
||||||
|
AttributeFormDataProcessor.process(formData, realm, user);
|
||||||
|
|
||||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
|
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
|
||||||
|
|
||||||
if (emailChanged) {
|
if (emailChanged) {
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -526,6 +526,8 @@ public class LoginActionsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AttributeFormDataProcessor.process(formData, realm, user);
|
||||||
|
|
||||||
event.user(user).success();
|
event.user(user).success();
|
||||||
event.reset();
|
event.reset();
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,11 @@ public class AccountTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@Test @Ignore
|
||||||
|
public void runit() throws Exception {
|
||||||
|
Thread.sleep(10000000);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void returnToAppFromQueryParam() {
|
public void returnToAppFromQueryParam() {
|
||||||
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
|
driver.navigate().to(AccountUpdateProfilePage.PATH + "?referrer=test-app");
|
||||||
|
@ -219,7 +224,7 @@ public class AccountTest {
|
||||||
|
|
||||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
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.open();
|
||||||
loginPage.login("test-user@localhost", "new-password");
|
loginPage.login("test-user@localhost", "new-password");
|
||||||
|
|
Loading…
Reference in a new issue