Merge pull request #3895 from stianst/KEYCLOAK-943
KEYCLOAK-943 Added initial implementation for update profile
This commit is contained in:
commit
07f99a946d
3 changed files with 161 additions and 70 deletions
|
@ -60,12 +60,7 @@ import org.keycloak.services.validation.Validation;
|
|||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.OPTIONS;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -259,27 +254,33 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
*/
|
||||
@Path("/")
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public Response accountPage() {
|
||||
List<MediaType> types = headers.getAcceptableMediaTypes();
|
||||
if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
|
||||
return forwardToPage(null, AccountPages.ACCOUNT);
|
||||
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
|
||||
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
||||
return forwardToPage(null, AccountPages.ACCOUNT);
|
||||
}
|
||||
|
||||
UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
|
||||
if (rep.getAttributes() != null) {
|
||||
Iterator<String> itr = rep.getAttributes().keySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (itr.next().startsWith("keycloak.")) {
|
||||
itr.remove();
|
||||
}
|
||||
/**
|
||||
* Get account information.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Path("/")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response accountPageJson() {
|
||||
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
||||
|
||||
UserRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, auth.getUser());
|
||||
if (rep.getAttributes() != null) {
|
||||
Iterator<String> itr = rep.getAttributes().keySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
if (itr.next().startsWith("keycloak.")) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
|
||||
} else {
|
||||
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
|
||||
}
|
||||
|
||||
return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
|
||||
}
|
||||
|
||||
public static UriBuilder totpUrl(UriBuilder base) {
|
||||
|
@ -377,6 +378,8 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
|
||||
UserModel user = auth.getUser();
|
||||
|
||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
|
||||
|
||||
List<FormMessage> errors = Validation.validateUpdateProfileForm(realm.isEditUsernameAllowed(), formData);
|
||||
if (errors != null && !errors.isEmpty()) {
|
||||
setReferrerOnPage();
|
||||
|
@ -384,49 +387,15 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
}
|
||||
|
||||
try {
|
||||
if (realm.isEditUsernameAllowed()) {
|
||||
String username = formData.getFirst("username");
|
||||
updateUsername(formData.getFirst("username"), user);
|
||||
updateEmail(formData.getFirst("email"), user);
|
||||
|
||||
UserModel existing = session.users().getUserByUsername(username, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||
}
|
||||
|
||||
user.setUsername(username);
|
||||
}
|
||||
user.setFirstName(formData.getFirst("firstName"));
|
||||
user.setLastName(formData.getFirst("lastName"));
|
||||
|
||||
String email = formData.getFirst("email");
|
||||
String oldEmail = user.getEmail();
|
||||
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
||||
if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
|
||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
user.setEmail(email);
|
||||
|
||||
AttributeFormDataProcessor.process(formData, realm, user);
|
||||
|
||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
|
||||
|
||||
if (emailChanged) {
|
||||
user.setEmailVerified(false);
|
||||
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||
}
|
||||
|
||||
if (realm.isRegistrationEmailAsUsername()) {
|
||||
if (!realm.isDuplicateEmailsAllowed()) {
|
||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||
}
|
||||
}
|
||||
user.setUsername(email);
|
||||
}
|
||||
event.success();
|
||||
|
||||
setReferrerOnPage();
|
||||
return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT);
|
||||
|
@ -439,6 +408,82 @@ public class AccountService extends AbstractSecuredLocalService {
|
|||
}
|
||||
}
|
||||
|
||||
@Path("/")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response processAccountUpdateJson(UserRepresentation userRep) {
|
||||
require(AccountRoles.MANAGE_ACCOUNT);
|
||||
if (auth.isCookieAuthenticated()) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
UserModel user = auth.getUser();
|
||||
|
||||
event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser());
|
||||
|
||||
updateUsername(userRep.getUsername(), user);
|
||||
updateEmail(userRep.getEmail(), user);
|
||||
|
||||
user.setFirstName(userRep.getFirstName());
|
||||
user.setLastName(userRep.getLastName());
|
||||
|
||||
if (userRep.getAttributes() != null) {
|
||||
for (String k : user.getAttributes().keySet()) {
|
||||
if (!userRep.getAttributes().containsKey(k)) {
|
||||
user.removeAttribute(k);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> e : userRep.getAttributes().entrySet()) {
|
||||
user.setAttribute(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
event.success();
|
||||
|
||||
return Cors.add(request, Response.ok()).build();
|
||||
}
|
||||
|
||||
private void updateUsername(String username, UserModel user) {
|
||||
if (realm.isEditUsernameAllowed() && username != null) {
|
||||
UserModel existing = session.users().getUserByUsername(username, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||
}
|
||||
|
||||
user.setUsername(username);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateEmail(String email, UserModel user) {
|
||||
String oldEmail = user.getEmail();
|
||||
boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
|
||||
if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
|
||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.EMAIL_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
user.setEmail(email);
|
||||
|
||||
if (emailChanged) {
|
||||
user.setEmailVerified(false);
|
||||
event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
|
||||
}
|
||||
|
||||
if (realm.isRegistrationEmailAsUsername()) {
|
||||
if (!realm.isDuplicateEmailsAllowed()) {
|
||||
UserModel existing = session.users().getUserByEmail(email, realm);
|
||||
if (existing != null && !existing.getId().equals(user.getId())) {
|
||||
throw new ModelDuplicateException(Messages.USERNAME_EXISTS);
|
||||
}
|
||||
}
|
||||
user.setUsername(email);
|
||||
}
|
||||
}
|
||||
|
||||
@Path("totp-remove")
|
||||
@GET
|
||||
public Response processTotpRemove(@QueryParam("stateChecker") String stateChecker) {
|
||||
|
|
|
@ -525,8 +525,8 @@ public class AccountTest extends AbstractTestRealmKeycloakTest {
|
|||
Assert.assertEquals("New last", profilePage.getLastName());
|
||||
Assert.assertEquals("new@email.com", profilePage.getEmail());
|
||||
|
||||
events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
|
||||
events.expectAccount(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||
events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
|
||||
|
||||
// reset user for other tests
|
||||
profilePage.updateProfile("Tom", "Brady", "test-user@localhost");
|
||||
|
|
|
@ -22,6 +22,8 @@ import org.apache.http.HttpHeaders;
|
|||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.jboss.arquillian.drone.api.annotation.Default;
|
||||
import org.jboss.arquillian.graphene.context.GrapheneContext;
|
||||
|
@ -45,12 +47,14 @@ import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
|||
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||
import org.keycloak.testsuite.pages.LoginPage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.runonserver.SerializationUtil;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.RealmRepUtil;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.Capabilities;
|
||||
import org.openqa.selenium.JavascriptExecutor;
|
||||
|
@ -68,6 +72,7 @@ import java.io.IOException;
|
|||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
@ -146,22 +151,48 @@ public class ProfileTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
HttpResponse response = doGetProfile(token, null);
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
|
||||
UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class);
|
||||
|
||||
assertEquals("test-user@localhost", profile.getString("username"));
|
||||
assertEquals("test-user@localhost", profile.getString("email"));
|
||||
assertEquals("First", profile.getString("firstName"));
|
||||
assertEquals("Last", profile.getString("lastName"));
|
||||
assertEquals("test-user@localhost", profile.getUsername());
|
||||
assertEquals("test-user@localhost", profile.getEmail());
|
||||
assertEquals("First", profile.getFirstName());
|
||||
assertEquals("Last", profile.getLastName());
|
||||
|
||||
JSONObject attributes = profile.getJSONObject("attributes");
|
||||
JSONArray attrValue = attributes.getJSONArray("key1");
|
||||
assertEquals(1, attrValue.length());
|
||||
Map<String, List<String>> attributes = profile.getAttributes();
|
||||
List<String> attrValue = attributes.get("key1");
|
||||
assertEquals(1, attrValue.size());
|
||||
assertEquals("value1", attrValue.get(0));
|
||||
attrValue = attributes.getJSONArray("key2");
|
||||
assertEquals(1, attrValue.length());
|
||||
attrValue = attributes.get("key2");
|
||||
assertEquals(1, attrValue.size());
|
||||
assertEquals("value2", attrValue.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateProfile() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
|
||||
|
||||
UserRepresentation user = new UserRepresentation();
|
||||
user.setUsername("test-user@localhost");
|
||||
user.setFirstName("NewFirst");
|
||||
user.setLastName("NewLast");
|
||||
user.setEmail("NewEmail@localhost");
|
||||
|
||||
HttpResponse response = doUpdateProfile(token, null, JsonSerialization.writeValueAsString(user));
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
|
||||
response = doGetProfile(token, null);
|
||||
|
||||
UserRepresentation profile = JsonSerialization.readValue(IOUtils.toString(response.getEntity().getContent()), UserRepresentation.class);
|
||||
|
||||
assertEquals("test-user@localhost", profile.getUsername());
|
||||
assertEquals("newemail@localhost", profile.getEmail());
|
||||
assertEquals("NewFirst", profile.getFirstName());
|
||||
assertEquals("NewLast", profile.getLastName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getProfileCors() throws Exception {
|
||||
oauth.doLogin("test-user@localhost", "password");
|
||||
|
@ -274,6 +305,21 @@ public class ProfileTest extends AbstractTestRealmKeycloakTest {
|
|||
return client.execute(get);
|
||||
}
|
||||
|
||||
private HttpResponse doUpdateProfile(String token, String origin, String value) throws IOException {
|
||||
HttpClient client = new DefaultHttpClient();
|
||||
HttpPost post = new HttpPost(UriBuilder.fromUri(getAccountURI()).build());
|
||||
if (token != null) {
|
||||
post.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token);
|
||||
}
|
||||
if (origin != null) {
|
||||
post.setHeader("Origin", origin);
|
||||
}
|
||||
post.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
|
||||
post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
|
||||
post.setEntity(new StringEntity(value));
|
||||
return client.execute(post);
|
||||
}
|
||||
|
||||
private String[] doGetProfileJs(String authServerRoot, String token) {
|
||||
UriBuilder uriBuilder = UriBuilder.fromUri(authServerRoot)
|
||||
.path(TestApplicationResource.class)
|
||||
|
|
Loading…
Reference in a new issue