keycloak-aplcache
Changes
services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java 4(+2 -2)
services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java 290(+93 -197)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountPasswordPage.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenUtil.java 85(+85 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WebDriverLogDumper.java 26(+26 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java 17(+8 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceCorsTest.java 180(+180 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java 198(+198 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java 362(+0 -362)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java 1(+0 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java 4(+2 -2)
Details
diff --git a/core/src/main/java/org/keycloak/representations/account/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/account/ClientRepresentation.java
new file mode 100644
index 0000000..c296426
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/account/ClientRepresentation.java
@@ -0,0 +1,25 @@
+package org.keycloak.representations.account;
+
+/**
+ * Created by st on 29/03/17.
+ */
+public class ClientRepresentation {
+ private String clientId;
+ private String clientName;
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientName() {
+ return clientName;
+ }
+
+ public void setClientName(String clientName) {
+ this.clientName = clientName;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java b/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java
new file mode 100644
index 0000000..8e4d96f
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/account/SessionRepresentation.java
@@ -0,0 +1,64 @@
+package org.keycloak.representations.account;
+
+import java.util.List;
+
+/**
+ * Created by st on 29/03/17.
+ */
+public class SessionRepresentation {
+
+ private String id;
+ private String ipAddress;
+ private int started;
+ private int lastAccess;
+ private int expires;
+ private List<ClientRepresentation> clients;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public int getStarted() {
+ return started;
+ }
+
+ public void setStarted(int started) {
+ this.started = started;
+ }
+
+ public int getLastAccess() {
+ return lastAccess;
+ }
+
+ public void setLastAccess(int lastAccess) {
+ this.lastAccess = lastAccess;
+ }
+
+ public int getExpires() {
+ return expires;
+ }
+
+ public void setExpires(int expires) {
+ this.expires = expires;
+ }
+
+ public List<ClientRepresentation> getClients() {
+ return clients;
+ }
+
+ public void setClients(List<ClientRepresentation> clients) {
+ this.clients = clients;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/account/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/account/UserRepresentation.java
new file mode 100755
index 0000000..f457b18
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/account/UserRepresentation.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.account;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.keycloak.json.StringListMapDeserializer;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserRepresentation {
+
+ private String id;
+ private String username;
+ private String firstName;
+ private String lastName;
+ private String email;
+ private boolean emailVerified;
+
+ @JsonDeserialize(using = StringListMapDeserializer.class)
+ private Map<String, List<String>> attributes;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public boolean isEmailVerified() {
+ return emailVerified;
+ }
+
+ public void setEmailVerified(boolean emailVerified) {
+ this.emailVerified = emailVerified;
+ }
+
+ public Map<String, List<String>> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map<String, List<String>> attributes) {
+ this.attributes = attributes;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
index 96f4257..54dd7bf 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountFederatedIdentityBean.java
@@ -23,7 +23,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.services.Urls;
import javax.ws.rs.core.UriBuilder;
@@ -80,7 +80,7 @@ public class AccountFederatedIdentityBean {
this.identities = new LinkedList<FederatedIdentityEntry>(orderedSet);
// Removing last social provider is not possible if you don't have other possibility to authenticate
- this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountService.isPasswordSet(session, realm, user);
+ this.removeLinkPossible = availableIdentities > 1 || user.getFederationLink() != null || AccountFormService.isPasswordSet(session, realm, user);
}
private FederatedIdentityModel getIdentity(Set<FederatedIdentityModel> identities, String providerId) {
diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
index 50972f4..bebcb2d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -58,6 +58,10 @@ public class AppAuthManager extends AuthenticationManager {
return authenticateBearerToken(session, realm, ctx.getUri(), ctx.getConnection(), ctx.getRequestHeaders());
}
+ public AuthResult authenticateBearerToken(KeycloakSession session) {
+ return authenticateBearerToken(session, session.getContext().getRealm(), session.getContext().getUri(), session.getContext().getConnection(), session.getContext().getRequestHeaders());
+ }
+
public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
String tokenString = extractAuthorizationHeaderToken(headers);
if (tokenString == null) return null;
diff --git a/services/src/main/java/org/keycloak/services/managers/Auth.java b/services/src/main/java/org/keycloak/services/managers/Auth.java
index 8b6086e..7c97c8f 100755
--- a/services/src/main/java/org/keycloak/services/managers/Auth.java
+++ b/services/src/main/java/org/keycloak/services/managers/Auth.java
@@ -23,6 +23,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessToken;
+import org.keycloak.services.ForbiddenException;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -79,6 +80,18 @@ public class Auth {
this.clientSession = clientSession;
}
+ public void require(String role) {
+ if (!hasClientRole(client, role)) {
+ throw new ForbiddenException();
+ }
+ }
+
+ public void requireOneOf(String... roles) {
+ if (!hasOneOfAppRole(client, roles)) {
+ throw new ForbiddenException();
+ }
+ }
+
public boolean hasRealmRole(String role) {
if (cookie) {
return user.hasRole(realm.getRole(role));
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 0235120..fa9fec6 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -136,6 +136,19 @@ public class AuthenticationManager {
}
+ public static void backchannelLogout(KeycloakSession session, UserSessionModel userSession, boolean logoutBroker) {
+ backchannelLogout(
+ session,
+ session.getContext().getRealm(),
+ userSession,
+ session.getContext().getUri(),
+ session.getContext().getConnection(),
+ session.getContext().getRequestHeaders(),
+ logoutBroker
+ );
+ }
+
+
/**
* Do not logout broker
*
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 180694a..35359f4 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -176,6 +176,8 @@ public class Messages {
public static final String READ_ONLY_USER = "readOnlyUserMessage";
+ public static final String READ_ONLY_USERNAME = "readOnlyUsernameMessage";
+
public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage";
public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
index cc8abfb..7b85795 100755
--- a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -203,32 +203,6 @@ public abstract class AbstractSecuredLocalService {
return oauth.redirect(uriInfo, accountUri.toString());
}
- protected Response authenticateBrowser() {
- AppAuthManager authManager = new AppAuthManager();
- AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
- if (authResult != null) {
- auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
- } else {
- return login(null);
- }
- // don't allow cors requests
- // This is to prevent CSRF attacks.
- String requestOrigin = UriUtils.getOrigin(uriInfo.getBaseUri());
- String origin = headers.getRequestHeaders().getFirst("Origin");
- if (origin != null && !requestOrigin.equals(origin)) {
- throw new ForbiddenException();
- }
-
- if (!request.getHttpMethod().equals("GET")) {
- String referrer = headers.getRequestHeaders().getFirst("Referer");
- if (referrer != null && !requestOrigin.equals(UriUtils.getOrigin(referrer))) {
- throw new ForbiddenException();
- }
- }
- updateCsrfChecks();
- return null;
- }
-
static class OAuthRedirect extends AbstractOAuthClient {
/**
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java
new file mode 100644
index 0000000..11c3161
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountLoader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.account;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.Auth;
+import org.keycloak.services.managers.AuthenticationManager;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.NotAuthorizedException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountLoader {
+
+ private static final Logger logger = Logger.getLogger(AccountLoader.class);
+
+ private AccountLoader() {
+ }
+
+ public static Object getAccountService(KeycloakSession session, EventBuilder event) {
+ RealmModel realm = session.getContext().getRealm();
+
+ ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+ if (client == null || !client.isEnabled()) {
+ logger.debug("account management not enabled");
+ throw new NotFoundException("account management not enabled");
+ }
+
+ HttpRequest request = session.getContext().getContextObject(HttpRequest.class);
+ HttpHeaders headers = session.getContext().getRequestHeaders();
+ MediaType content = headers.getMediaType();
+ List<MediaType> accepts = headers.getAcceptableMediaTypes();
+
+ if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
+ return new CorsPreflightService(request);
+ } else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
+ AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session);
+ if (authResult == null) {
+ throw new NotAuthorizedException("Bearer token required");
+ }
+
+ Auth auth = new Auth(session.getContext().getRealm(), authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false);
+ AccountRestService accountRestService = new AccountRestService(session, auth, client, event);
+ ResteasyProviderFactory.getInstance().injectProperties(accountRestService);
+ accountRestService.init();
+ return accountRestService;
+ } else {
+ AccountFormService accountFormService = new AccountFormService(realm, client, event);
+ ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
+ accountFormService.init();
+ return accountFormService;
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
new file mode 100755
index 0000000..e327519
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.account;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventStoreProvider;
+import org.keycloak.events.EventType;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.account.ClientRepresentation;
+import org.keycloak.representations.account.SessionRepresentation;
+import org.keycloak.representations.account.UserRepresentation;
+import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.managers.Auth;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.resources.Cors;
+import org.keycloak.storage.ReadOnlyException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountRestService {
+
+ @Context
+ private HttpRequest request;
+ @Context
+ protected UriInfo uriInfo;
+ @Context
+ protected HttpHeaders headers;
+ @Context
+ protected ClientConnection clientConnection;
+
+ private final KeycloakSession session;
+ private final ClientModel client;
+ private final EventBuilder event;
+ private EventStoreProvider eventStore;
+ private Auth auth;
+
+ private final RealmModel realm;
+ private final UserModel user;
+
+ public AccountRestService(KeycloakSession session, Auth auth, ClientModel client, EventBuilder event) {
+ this.session = session;
+ this.auth = auth;
+ this.realm = auth.getRealm();
+ this.user = auth.getUser();
+ this.client = client;
+ this.event = event;
+ }
+
+ public void init() {
+ eventStore = session.getProvider(EventStoreProvider.class);
+ }
+
+ /**
+ * CORS preflight
+ *
+ * @return
+ */
+ @Path("/")
+ @OPTIONS
+ @NoCache
+ public Response preflight() {
+ return Cors.add(request, Response.ok()).auth().preflight().build();
+ }
+
+ /**
+ * Get account information.
+ *
+ * @return
+ */
+ @Path("/")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public Response account() {
+ auth.requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
+
+ UserModel user = auth.getUser();
+
+ UserRepresentation rep = new UserRepresentation();
+ rep.setUsername(user.getUsername());
+ rep.setFirstName(user.getFirstName());
+ rep.setLastName(user.getLastName());
+ rep.setEmail(user.getEmail());
+ rep.setEmailVerified(user.isEmailVerified());
+ rep.setAttributes(user.getAttributes());
+
+ return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
+ }
+
+ @Path("/")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public Response updateAccount(UserRepresentation userRep) {
+ auth.require(AccountRoles.MANAGE_ACCOUNT);
+
+ event.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(user);
+
+ try {
+ RealmModel realm = session.getContext().getRealm();
+
+ boolean usernameChanged = userRep.getUsername() != null && !userRep.getUsername().equals(user.getUsername());
+ if (realm.isEditUsernameAllowed()) {
+ if (usernameChanged) {
+ UserModel existing = session.users().getUserByUsername(userRep.getUsername(), realm);
+ if (existing != null) {
+ return ErrorResponse.exists(Errors.USERNAME_EXISTS);
+ }
+
+ user.setUsername(userRep.getUsername());
+ }
+ } else if (usernameChanged) {
+ return ErrorResponse.error(Errors.READ_ONLY_USERNAME, Response.Status.BAD_REQUEST);
+ }
+
+ boolean emailChanged = userRep.getEmail() != null && !userRep.getEmail().equals(user.getEmail());
+ if (emailChanged && !realm.isDuplicateEmailsAllowed()) {
+ UserModel existing = session.users().getUserByEmail(userRep.getEmail(), realm);
+ if (existing != null) {
+ return ErrorResponse.exists(Errors.EMAIL_EXISTS);
+ }
+ }
+
+ if (realm.isRegistrationEmailAsUsername() && !realm.isDuplicateEmailsAllowed()) {
+ UserModel existing = session.users().getUserByUsername(userRep.getEmail(), realm);
+ if (existing != null) {
+ return ErrorResponse.exists(Errors.USERNAME_EXISTS);
+ }
+ }
+
+ if (emailChanged) {
+ String oldEmail = user.getEmail();
+ user.setEmail(userRep.getEmail());
+ user.setEmailVerified(false);
+ event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, userRep.getEmail()).success();
+
+ if (realm.isRegistrationEmailAsUsername()) {
+ user.setUsername(userRep.getEmail());
+ }
+ }
+
+ 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()).auth().allowedOrigins(auth.getToken()).build();
+ } catch (ReadOnlyException e) {
+ return ErrorResponse.error(Errors.READ_ONLY_USER, Response.Status.BAD_REQUEST);
+ }
+ }
+
+ /**
+ * Get session information.
+ *
+ * @return
+ */
+ @Path("/sessions")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public Response sessions() {
+ List<SessionRepresentation> reps = new LinkedList<>();
+
+ List<UserSessionModel> sessions = session.sessions().getUserSessions(realm, user);
+ for (UserSessionModel s : sessions) {
+ SessionRepresentation rep = new SessionRepresentation();
+ rep.setId(s.getId());
+ rep.setIpAddress(s.getIpAddress());
+ rep.setStarted(s.getStarted());
+ rep.setLastAccess(s.getLastSessionRefresh());
+ rep.setExpires(s.getStarted() + realm.getSsoSessionMaxLifespan());
+ rep.setClients(new LinkedList());
+
+ for (String clientUUID : s.getAuthenticatedClientSessions().keySet()) {
+ ClientModel client = realm.getClientById(clientUUID);
+ ClientRepresentation clientRep = new ClientRepresentation();
+ clientRep.setClientId(client.getClientId());
+ clientRep.setClientName(client.getName());
+ rep.getClients().add(clientRep);
+ }
+
+ reps.add(rep);
+ }
+
+ return Cors.add(request, Response.ok(reps)).auth().allowedOrigins(auth.getToken()).build();
+ }
+
+ /**
+ * Remove sessions
+ *
+ * @param removeCurrent remove current session (default is false)
+ * @return
+ */
+ @Path("/sessions")
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public Response sessionsLogout(@QueryParam("current") boolean removeCurrent) {
+ UserSessionModel userSession = auth.getSession();
+
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
+ for (UserSessionModel s : userSessions) {
+ if (removeCurrent || !s.getId().equals(userSession.getId())) {
+ AuthenticationManager.backchannelLogout(session, s, true);
+ }
+ }
+
+ return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build();
+ }
+
+ // TODO Federated identities
+ // TODO Applications
+ // TODO Logs
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/Constants.java b/services/src/main/java/org/keycloak/services/resources/account/Constants.java
new file mode 100644
index 0000000..95b76de
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/Constants.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.account;
+
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Constants {
+
+ public static final EventType[] EXPOSED_LOG_EVENTS = {
+ EventType.LOGIN, EventType.LOGOUT, EventType.REGISTER, EventType.REMOVE_FEDERATED_IDENTITY, EventType.REMOVE_TOTP, EventType.SEND_RESET_PASSWORD,
+ EventType.SEND_VERIFY_EMAIL, EventType.FEDERATED_IDENTITY_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL
+ };
+
+ public static final Set<String> EXPOSED_LOG_DETAILS = new HashSet<>();
+
+ static {
+ EXPOSED_LOG_DETAILS.add(Details.UPDATED_EMAIL);
+ EXPOSED_LOG_DETAILS.add(Details.EMAIL);
+ EXPOSED_LOG_DETAILS.add(Details.PREVIOUS_EMAIL);
+ EXPOSED_LOG_DETAILS.add(Details.USERNAME);
+ EXPOSED_LOG_DETAILS.add(Details.REMEMBER_ME);
+ EXPOSED_LOG_DETAILS.add(Details.REGISTER_METHOD);
+ EXPOSED_LOG_DETAILS.add(Details.AUTH_METHOD);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/CorsPreflightService.java b/services/src/main/java/org/keycloak/services/resources/account/CorsPreflightService.java
new file mode 100644
index 0000000..f9c0fa6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/CorsPreflightService.java
@@ -0,0 +1,33 @@
+package org.keycloak.services.resources.account;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.services.resources.Cors;
+
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Response;
+
+/**
+ * Created by st on 21/03/17.
+ */
+public class CorsPreflightService {
+
+ private HttpRequest request;
+
+ public CorsPreflightService(HttpRequest request) {
+ this.request = request;
+ }
+
+ /**
+ * CORS preflight
+ *
+ * @return
+ */
+ @Path("/")
+ @OPTIONS
+ public Response preflight() {
+ Cors cors = Cors.add(request, Response.ok()).auth().allowedMethods("GET", "POST", "HEAD", "OPTIONS").preflight();
+ return cors.build();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/Errors.java b/services/src/main/java/org/keycloak/services/resources/account/Errors.java
new file mode 100644
index 0000000..6cb55bd
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/account/Errors.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.services.resources.account;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Errors {
+
+ public static final String USERNAME_EXISTS = "username_exists";
+ public static final String EMAIL_EXISTS = "email_exists";
+ public static final String READ_ONLY_USER = "user_read_only";
+ public static final String READ_ONLY_USERNAME = "username_read_only";
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
index 21943cc..5af5beb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java
@@ -68,8 +68,8 @@ import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.UserSessionManager;
-import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.LoginActionsService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.validation.Validation;
import org.keycloak.storage.ReadOnlyException;
@@ -282,7 +282,7 @@ public class UserResource {
String sessionId = KeycloakModelUtils.generateId();
UserSessionModel userSession = session.sessions().createUserSession(sessionId, realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null);
AuthenticationManager.createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
- URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName());
+ URI redirect = AccountFormService.accountServiceApplicationPage(uriInfo).build(realm.getName());
Map<String, Object> result = new HashMap<>();
result.put("sameRealm", sameRealm);
result.put("redirect", redirect.toString());
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 c7b9945..578885d 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
@@ -68,7 +68,6 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.*;
import org.keycloak.services.managers.*;
-import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.validation.Validation;
import org.keycloak.storage.ReadOnlyException;
diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java
index c9bfa03..b647c75 100755
--- a/services/src/main/java/org/keycloak/services/resources/Cors.java
+++ b/services/src/main/java/org/keycloak/services/resources/Cors.java
@@ -108,28 +108,32 @@ public class Cors {
public Cors allowedOrigins(String... allowedOrigins) {
if (allowedOrigins != null && allowedOrigins.length > 0) {
- this.allowedOrigins = new HashSet<String>(Arrays.asList(allowedOrigins));
+ this.allowedOrigins = new HashSet<>(Arrays.asList(allowedOrigins));
}
return this;
}
public Cors allowedMethods(String... allowedMethods) {
- this.allowedMethods = new HashSet<String>(Arrays.asList(allowedMethods));
+ this.allowedMethods = new HashSet<>(Arrays.asList(allowedMethods));
return this;
}
public Cors exposedHeaders(String... exposedHeaders) {
- this.exposedHeaders = new HashSet<String>(Arrays.asList(exposedHeaders));
+ this.exposedHeaders = new HashSet<>(Arrays.asList(exposedHeaders));
return this;
}
public Response build() {
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
if (origin == null) {
+ logger.trace("No origin header ignoring");
return builder.build();
}
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
+ if (logger.isDebugEnabled()) {
+ logger.debugv("Invalid CORS request: origin {0} not in allowed origins {1}", origin, Arrays.toString(allowedOrigins.toArray()));
+ }
return builder.build();
}
@@ -165,23 +169,25 @@ public class Cors {
builder.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
}
+ logger.debug("Added CORS headers to response");
+
return builder.build();
}
public void build(HttpResponse response) {
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
if (origin == null) {
- logger.debug("No origin returning");
+ logger.trace("No origin header ignoring");
return;
}
if (!preflight && (allowedOrigins == null || (!allowedOrigins.contains(origin) && !allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)))) {
- logger.debug("!preflight and no origin");
+ if (logger.isDebugEnabled()) {
+ logger.debugv("Invalid CORS request: origin {0} not in allowed origins {1}", origin, Arrays.toString(allowedOrigins.toArray()));
+ }
return;
}
- logger.debug("build CORS headers and return");
-
if (allowedOrigins.contains(ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD)) {
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_WILDCARD);
} else {
@@ -213,6 +219,8 @@ public class Cors {
if (preflight) {
response.getOutputHeaders().add(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
}
+
+ logger.debug("Added CORS headers to response");
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 7961163..2ef481e 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -37,7 +37,6 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time;
-import org.keycloak.common.util.UriUtils;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@@ -77,6 +76,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.validation.Validation;
@@ -1082,7 +1082,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
FormMessage errorMessage = new FormMessage(message, parameters);
try {
String serializedError = JsonSerialization.writeValueAsString(errorMessage);
- authSession.setAuthNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
+ authSession.setAuthNote(AccountFormService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
index 7526139..2890229 100755
--- a/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/PublicRealmResource.java
@@ -25,6 +25,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.idm.PublishedRealmRepresentation;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.services.resources.admin.AdminRoot;
import javax.ws.rs.GET;
@@ -91,7 +92,7 @@ public class PublicRealmResource {
PublishedRealmRepresentation rep = new PublishedRealmRepresentation();
rep.setRealm(realm.getName());
rep.setTokenServiceUrl(OIDCLoginProtocolService.tokenServiceBaseUrl(uriInfo).build(realm.getName()).toString());
- rep.setAccountServiceUrl(AccountService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
+ rep.setAccountServiceUrl(AccountFormService.accountServiceBaseUrl(uriInfo).build(realm.getName()).toString());
rep.setAdminApiUrl(uriInfo.getBaseUriBuilder().path(AdminRoot.class).build().toString());
rep.setPublicKeyPem(PemUtils.encodeKey(session.keys().getActiveRsaKey(realm).getPublicKey()));
rep.setNotBefore(realm.getNotBefore());
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index bc3f8dc..18a6fd9 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -26,7 +26,6 @@ import org.keycloak.common.Profile;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.LoginProtocol;
@@ -34,6 +33,7 @@ import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.services.clientregistration.ClientRegistrationService;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
+import org.keycloak.services.resources.account.AccountLoader;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.utils.ProfileHelper;
@@ -206,20 +206,10 @@ public class RealmsResource {
}
@Path("{realm}/account")
- public AccountService getAccountService(final @PathParam("realm") String name) {
+ public Object getAccountService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
-
- ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
- if (client == null || !client.isEnabled()) {
- logger.debug("account management not enabled");
- throw new NotFoundException("account management not enabled");
- }
-
EventBuilder event = new EventBuilder(realm, session, clientConnection);
- AccountService accountService = new AccountService(realm, client, event);
- ResteasyProviderFactory.getInstance().injectProperties(accountService);
- accountService.init();
- return accountService;
+ return AccountLoader.getAccountService(session, event);
}
@Path("{realm}")
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 51f505e..cb023fa 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -21,7 +21,7 @@ import org.keycloak.common.Version;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.services.resources.IdentityBrokerService;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.resources.RealmsResource;
@@ -41,7 +41,7 @@ public class Urls {
}
public static URI accountApplicationsPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "applicationsPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "applicationsPage").build(realmName);
}
public static UriBuilder accountBase(URI baseUri) {
@@ -53,19 +53,19 @@ public class Urls {
}
public static UriBuilder accountPageBuilder(URI baseUri) {
- return accountBase(baseUri).path(AccountService.class, "accountPage");
+ return accountBase(baseUri).path(AccountFormService.class, "accountPage");
}
public static URI accountPasswordPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "passwordPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "passwordPage").build(realmName);
}
public static URI accountFederatedIdentityPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "federatedIdentityPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "federatedIdentityPage").build(realmName);
}
public static URI accountFederatedIdentityUpdate(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "processFederatedIdentityUpdate").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "processFederatedIdentityUpdate").build(realmName);
}
public static URI identityProviderAuthnResponse(URI baseUri, String providerId, String realmName) {
@@ -123,31 +123,31 @@ public class Urls {
}
public static URI accountTotpPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "totpPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "totpPage").build(realmName);
}
public static URI accountTotpRemove(URI baseUri, String realmName, String stateChecker) {
- return accountBase(baseUri).path(AccountService.class, "processTotpRemove")
+ return accountBase(baseUri).path(AccountFormService.class, "processTotpRemove")
.queryParam("stateChecker", stateChecker)
.build(realmName);
}
public static URI accountLogPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "logPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "logPage").build(realmName);
}
public static URI accountSessionsPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "sessionsPage").build(realmName);
+ return accountBase(baseUri).path(AccountFormService.class, "sessionsPage").build(realmName);
}
public static URI accountSessionsLogoutPage(URI baseUri, String realmName, String stateChecker) {
- return accountBase(baseUri).path(AccountService.class, "processSessionsLogout")
+ return accountBase(baseUri).path(AccountFormService.class, "processSessionsLogout")
.queryParam("stateChecker", stateChecker)
.build(realmName);
}
public static URI accountRevokeClientPage(URI baseUri, String realmName) {
- return accountBase(baseUri).path(AccountService.class, "processRevokeGrant")
+ return accountBase(baseUri).path(AccountFormService.class, "processRevokeGrant")
.build(realmName);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 1e1da65..c334074 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -16,7 +16,7 @@
*/
package org.keycloak.testsuite.pages;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.testsuite.Constants;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -70,6 +70,6 @@ public class AccountPasswordPage extends AbstractAccountPage {
}
public String getPath() {
- return AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
+ return AccountFormService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build(this.realmName).toString();
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
index a715c56..8d1dfac 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java
@@ -16,7 +16,7 @@
*/
package org.keycloak.testsuite.pages;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.testsuite.Constants;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -28,7 +28,7 @@ import javax.ws.rs.core.UriBuilder;
*/
public class AccountTotpPage extends AbstractAccountPage {
- private static String PATH = AccountService.totpUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
+ private static String PATH = AccountFormService.totpUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
@FindBy(id = "totpSecret")
private WebElement totpSecret;
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 2c98c55..d62806f 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -16,7 +16,7 @@
*/
package org.keycloak.testsuite.pages;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -69,6 +69,6 @@ public class AccountPasswordPage extends AbstractAccountPage {
}
public String getPath() {
- return AccountService.passwordUrl(UriBuilder.fromUri(getAuthServerRoot())).build(this.realmName).toString();
+ return AccountFormService.passwordUrl(UriBuilder.fromUri(getAuthServerRoot())).build(this.realmName).toString();
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java
index 1029e10..6527476 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java
@@ -16,7 +16,7 @@
*/
package org.keycloak.testsuite.pages;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -40,7 +40,7 @@ public class AccountTotpPage extends AbstractAccountPage {
private WebElement removeLink;
private String getPath() {
- return AccountService.totpUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString();
+ return AccountFormService.totpUrl(UriBuilder.fromUri(getAuthServerRoot())).build("test").toString();
}
public void configure(String totp) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenUtil.java
new file mode 100644
index 0000000..a75f5cd
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/TokenUtil.java
@@ -0,0 +1,85 @@
+package org.keycloak.testsuite.util;
+
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+import org.keycloak.common.util.Time;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Created by st on 22/03/17.
+ */
+public class TokenUtil implements TestRule {
+
+ private final String username;
+ private final String password;
+ private OAuthClient oauth;
+
+ private String refreshToken;
+ private String token;
+ private int expires;
+
+ public TokenUtil() {
+ this("test-user@localhost", "password");
+ }
+
+ public TokenUtil(String username, String password) {
+ this.username = username;
+ this.password = password;
+ this.oauth = new OAuthClient();
+ this.oauth.init(null, null);
+ this.oauth.clientId("direct-grant");
+ }
+
+ @Override
+ public Statement apply(final Statement base, org.junit.runner.Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+ }
+ };
+ }
+
+ public String getToken() {
+ if (refreshToken == null) {
+ load();
+ } else if (expires < Time.currentTime()) {
+ refresh();
+ }
+ return token;
+ }
+
+ private void load() {
+ try {
+ OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doGrantAccessTokenRequest("password", username, password);
+ if (accessTokenResponse.getStatusCode() != 200) {
+ fail("Failed to get token: " + accessTokenResponse.getErrorDescription());
+ }
+
+ this.refreshToken = accessTokenResponse.getRefreshToken();
+ this.token = accessTokenResponse.getAccessToken();
+
+ expires = Time.currentTime() + accessTokenResponse.getExpiresIn() - 20;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void refresh() {
+ try {
+ OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doRefreshTokenRequest(refreshToken, "password");
+ if (accessTokenResponse.getStatusCode() != 200) {
+ fail("Failed to get token: " + accessTokenResponse.getErrorDescription());
+ }
+
+ this.refreshToken = accessTokenResponse.getRefreshToken();
+ this.token = accessTokenResponse.getAccessToken();
+
+ expires = Time.currentTime() + accessTokenResponse.getExpiresIn() - 20;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WebDriverLogDumper.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WebDriverLogDumper.java
new file mode 100644
index 0000000..f56a119
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WebDriverLogDumper.java
@@ -0,0 +1,26 @@
+package org.keycloak.testsuite.util;
+
+import org.jboss.logging.Logger;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.logging.LogEntries;
+import org.openqa.selenium.logging.LogEntry;
+
+/**
+ * Created by st on 21/03/17.
+ */
+public class WebDriverLogDumper {
+
+ public static String dumpBrowserLogs(WebDriver driver) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ LogEntries logEntries = driver.manage().logs().get("browser");
+ for (LogEntry e : logEntries.getAll()) {
+ sb.append("\n\t" + e.getMessage());
+ }
+ return sb.toString();
+ } catch (UnsupportedOperationException e) {
+ return "Browser doesn't support fetching logs";
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceCorsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceCorsTest.java
new file mode 100755
index 0000000..3526386
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceCorsTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.account;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.TokenUtil;
+import org.keycloak.testsuite.util.WebDriverLogDumper;
+import org.keycloak.util.JsonSerialization;
+import org.openqa.selenium.JavascriptExecutor;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountRestServiceCorsTest extends AbstractTestRealmKeycloakTest {
+
+ private static final String VALID_CORS_URL = "http://localtest.me:8180/auth";
+ private static final String INVALID_CORS_URL = "http://invalid.localtest.me:8180/auth";
+
+ @Rule
+ public TokenUtil tokenUtil = new TokenUtil();
+
+ private CloseableHttpClient client;
+ private JavascriptExecutor executor;
+
+ @Before
+ public void before() {
+ client = HttpClientBuilder.create().build();
+ oauth.clientId("direct-grant");
+ executor = (JavascriptExecutor) driver;
+ }
+
+ @After
+ public void after() {
+ try {
+ client.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ }
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Test
+ public void testGetProfile() throws IOException, InterruptedException {
+ driver.navigate().to(VALID_CORS_URL);
+
+ doJsGet(executor, getAccountUrl(), tokenUtil.getToken(), true);
+ }
+
+ @Test
+ public void testGetProfileInvalidOrigin() throws IOException, InterruptedException {
+ driver.navigate().to(INVALID_CORS_URL);
+
+ doJsGet(executor, getAccountUrl(), tokenUtil.getToken(), false);
+ }
+
+ @Test
+ public void testUpdateProfile() throws IOException {
+ driver.navigate().to(VALID_CORS_URL);
+
+ doJsPost(executor, getAccountUrl(), tokenUtil.getToken(), "{ \"firstName\" : \"Bob\" }", true);
+ }
+
+ @Test
+ public void testUpdateProfileInvalidOrigin() throws IOException {
+ driver.navigate().to(INVALID_CORS_URL);
+
+ doJsPost(executor, getAccountUrl(), tokenUtil.getToken(), "{ \"firstName\" : \"Bob\" }", false);
+ }
+
+ private String getAccountUrl() {
+ return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account";
+ }
+
+ private Result doJsGet(JavascriptExecutor executor, String url, String token, boolean expectAllowed) {
+ String js = "var r = new XMLHttpRequest();" +
+ "var r = new XMLHttpRequest();" +
+ "r.open('GET', '" + url + "', false);" +
+ "r.setRequestHeader('Accept','application/json');" +
+ "r.setRequestHeader('Authorization','bearer " + token + "');" +
+ "r.send();" +
+ "return r.status + ':::' + r.responseText";
+ return doXhr(executor, js, expectAllowed);
+ }
+
+ private Result doJsPost(JavascriptExecutor executor, String url, String token, String data, boolean expectAllowed) {
+ String js = "var r = new XMLHttpRequest();" +
+ "var r = new XMLHttpRequest();" +
+ "r.open('POST', '" + url + "', false);" +
+ "r.setRequestHeader('Accept','application/json');" +
+ "r.setRequestHeader('Content-Type','application/json');" +
+ "r.setRequestHeader('Authorization','bearer " + token + "');" +
+ "r.send('" + data + "');" +
+ "return r.status + ':::' + r.responseText";
+ return doXhr(executor, js, expectAllowed);
+ }
+
+ private Result doXhr(JavascriptExecutor executor, String js, boolean expectAllowed) {
+ Result result = null;
+ Throwable error = null;
+ try {
+ String response = (String) executor.executeScript(js);
+ String r[] = response.split(":::");
+ result = new Result(Integer.parseInt(r[0]), r.length == 2 ? r[1] : null);
+ } catch (Throwable t ) {
+ error = t;
+ }
+
+ if (result == null || result.getStatus() != 200 || error != null) {
+ if (expectAllowed) {
+ throw new AssertionError("Cors request failed: " + WebDriverLogDumper.dumpBrowserLogs(driver));
+ } else {
+ return result;
+ }
+ } else {
+ if (!expectAllowed) {
+ throw new AssertionError("Expected CORS request to be rejected, but was successful");
+ } else {
+ return result;
+ }
+ }
+ }
+
+ private static class Result {
+ int status;
+
+ String result;
+
+ public Result(int status, String result) {
+ this.status = status;
+ this.result = result;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public String getResult() {
+ return result;
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
new file mode 100755
index 0000000..e4f4ec1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.testsuite.account;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.representations.account.SessionRepresentation;
+import org.keycloak.representations.account.UserRepresentation;
+import org.keycloak.representations.idm.ErrorRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.util.TokenUtil;
+import org.keycloak.testsuite.util.UserBuilder;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest {
+
+ @Rule
+ public TokenUtil tokenUtil = new TokenUtil();
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ private CloseableHttpClient client;
+
+ @Before
+ public void before() {
+ client = HttpClientBuilder.create().build();
+ }
+
+ @After
+ public void after() {
+ try {
+ client.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ testRealm.getUsers().add(UserBuilder.create().username("no-account-access").password("password").build());
+ testRealm.getUsers().add(UserBuilder.create().username("view-account-access").role("account", "view-profile").password("password").build());
+ }
+
+ @Test
+ public void testGetProfile() throws IOException {
+ UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
+ assertEquals("Tom", user.getFirstName());
+ assertEquals("Brady", user.getLastName());
+ assertEquals("test-user@localhost", user.getEmail());
+ assertFalse(user.isEmailVerified());
+ assertTrue(user.getAttributes().isEmpty());
+ }
+
+ @Test
+ public void testUpdateProfile() throws IOException {
+ UserRepresentation user = SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
+ user.setFirstName("Homer");
+ user.setLastName("Simpsons");
+ user.getAttributes().put("attr1", Collections.singletonList("val1"));
+ user.getAttributes().put("attr2", Collections.singletonList("val2"));
+
+ user = updateAndGet(user);
+
+ assertEquals("Homer", user.getFirstName());
+ assertEquals("Simpsons", user.getLastName());
+ assertEquals(2, user.getAttributes().size());
+ assertEquals(1, user.getAttributes().get("attr1").size());
+ assertEquals("val1", user.getAttributes().get("attr1").get(0));
+ assertEquals(1, user.getAttributes().get("attr2").size());
+ assertEquals("val2", user.getAttributes().get("attr2").get(0));
+
+ // Update attributes
+ user.getAttributes().remove("attr1");
+ user.getAttributes().get("attr2").add("val3");
+
+ user = updateAndGet(user);
+
+ assertEquals(1, user.getAttributes().size());
+ assertEquals(2, user.getAttributes().get("attr2").size());
+ assertEquals("val2", user.getAttributes().get("attr2").get(0));
+ assertEquals("val3", user.getAttributes().get("attr2").get(1));
+
+ // Update email
+ user.setEmail("bobby@localhost");
+ user = updateAndGet(user);
+ assertEquals("bobby@localhost", user.getEmail());
+
+ user.setEmail("john-doh@localhost");
+ updateError(user, 409, "email_exists");
+
+ user.setEmail("test-user@localhost");
+ user = updateAndGet(user);
+ assertEquals("test-user@localhost", user.getEmail());
+
+ // Update username
+ user.setUsername("updatedUsername");
+ user = updateAndGet(user);
+ assertEquals("updatedusername", user.getUsername());
+
+ user.setUsername("john-doh@localhost");
+ updateError(user, 409, "username_exists");
+
+ user.setUsername("test-user@localhost");
+ user = updateAndGet(user);
+ assertEquals("test-user@localhost", user.getUsername());
+
+ RealmRepresentation realmRep = adminClient.realm("test").toRepresentation();
+ realmRep.setEditUsernameAllowed(false);
+ adminClient.realm("test").update(realmRep);
+
+ user.setUsername("updatedUsername2");
+ updateError(user, 400, "username_read_only");
+ }
+
+ private UserRepresentation updateAndGet(UserRepresentation user) throws IOException {
+ int status = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asStatus();
+ assertEquals(200, status);
+ return SimpleHttp.doGet(getAccountUrl(null), client).auth(tokenUtil.getToken()).asJson(UserRepresentation.class);
+ }
+
+
+ private void updateError(UserRepresentation user, int expectedStatus, String expectedMessage) throws IOException {
+ SimpleHttp.Response response = SimpleHttp.doPost(getAccountUrl(null), client).auth(tokenUtil.getToken()).json(user).asResponse();
+ assertEquals(expectedStatus, response.getStatus());
+ assertEquals(expectedMessage, response.asJson(ErrorRepresentation.class).getErrorMessage());
+ }
+
+ @Test
+ public void testProfilePermissions() throws IOException {
+ TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
+ TokenUtil viewToken = new TokenUtil("view-account-access", "password");
+
+ // Read with no access
+ assertEquals(403, SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus());
+
+ // Update with no access
+ assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(noaccessToken.getToken()).json(new UserRepresentation()).asStatus());
+
+ // Update with read only
+ assertEquals(403, SimpleHttp.doPost(getAccountUrl(null), client).auth(viewToken.getToken()).json(new UserRepresentation()).asStatus());
+ }
+
+ @Test
+ public void testUpdateProfilePermissions() throws IOException {
+ TokenUtil noaccessToken = new TokenUtil("no-account-access", "password");
+ int status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(noaccessToken.getToken()).asStatus();
+ assertEquals(403, status);
+
+ TokenUtil viewToken = new TokenUtil("view-account-access", "password");
+ status = SimpleHttp.doGet(getAccountUrl(null), client).header("Accept", "application/json").auth(viewToken.getToken()).asStatus();
+ assertEquals(200, status);
+ }
+
+ @Test
+ public void testGetSessions() throws IOException {
+ List<SessionRepresentation> sessions = SimpleHttp.doGet(getAccountUrl("sessions"), client).auth(tokenUtil.getToken()).asJson(new TypeReference<List<SessionRepresentation>>() {});
+
+ assertEquals(1, sessions.size());
+ }
+
+ private String getAccountUrl(String resource) {
+ return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/test/account" + (resource != null ? "/" + resource : "");
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
index 81942fe..d9e30b3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
@@ -27,7 +27,7 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
-import org.keycloak.testsuite.account.AccountTest;
+import org.keycloak.testsuite.account.AccountFormServiceTest;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.RealmBuilder;
@@ -68,7 +68,7 @@ public class CustomThemeTest extends AbstractTestRealmKeycloakTest {
profilePage.open();
loginPage.login("test-user@localhost", "password");
- events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent();
+ events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountFormServiceTest.ACCOUNT_REDIRECT).assertEvent();
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
Assert.assertEquals("", profilePage.getAttribute("street"));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java
index 1e349d9..f2448e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java
@@ -28,7 +28,7 @@ import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.account.AccountFormService;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.pages.AccountTotpPage;
@@ -45,7 +45,7 @@ import java.util.List;
public class UserTotpTest extends AbstractTestRealmKeycloakTest {
private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
- public static String ACCOUNT_REDIRECT = AccountService.loginRedirectUrl(BASE.clone()).build("test").toString();
+ public static String ACCOUNT_REDIRECT = AccountFormService.loginRedirectUrl(BASE.clone()).build("test").toString();
@Rule
public AssertEvents events = new AssertEvents(this);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index 926ff69..d0e0517 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -38,7 +38,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.account.AccountTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.AppPage;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index c0ca7f1..6254b51 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -43,7 +43,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.account.AccountTest;
+import org.keycloak.testsuite.account.AccountFormServiceTest;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.auth.page.AuthRealm;
@@ -530,7 +530,7 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
// Go to account mgmt applications page
applicationsPage.open();
loginPage.login("test-user@localhost", "password");
- events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT + "?path=applications").assertEvent();
+ events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountFormServiceTest.ACCOUNT_REDIRECT + "?path=applications").assertEvent();
Assert.assertTrue(applicationsPage.isCurrent());
Map<String, AccountApplicationsPage.AppEntry> apps = applicationsPage.getApplications();
Assert.assertTrue(apps.containsKey("offline-client-2"));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
index 2ce6b39..99cd578 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json
@@ -358,6 +358,13 @@
],
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
"secret": "password"
+ },
+ {
+ "clientId": "direct-grant",
+ "enabled": true,
+ "directAccessGrantsEnabled": true,
+ "secret": "password",
+ "webOrigins": [ "http://localtest.me:8180" ]
}
],
"roles" : {
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 906c525..c5ce32a 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -119,6 +119,7 @@ usernameExistsMessage=Username already exists.
emailExistsMessage=Email already exists.
readOnlyUserMessage=You can''t update your account as it is read only.
+readOnlyUsernameMessage=You can''t update your username as it is read only.
readOnlyPasswordMessage=You can''t update your password as your account is read only.
successTotpMessage=Mobile authenticator configured.