From d79d2990739411674bfaff4ca0f1cb875e2781c7 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Sat, 19 Oct 2013 16:24:58 +0100 Subject: [PATCH] Made account management an optional feature on a realm, and added login and logout to account management pages --- .../admin/partials/realm-detail.html | 14 + .../org/keycloak/jaxrs/JaxrsOAuthClient.java | 14 +- .../idm/RealmRepresentation.java | 9 + examples/js/testrealm.json | 1 + .../main/java/org/keycloak/forms/UrlBean.java | 4 + .../forms/theme/default/template-main.ftl | 3 + .../java/org/keycloak/models/Constants.java | 2 + .../managers/AuthenticationManager.java | 16 ++ .../services/managers/RealmManager.java | 37 +++ .../managers/ResourceAdminManager.java | 22 +- .../services/resources/AccountService.java | 251 ++++++++++++------ .../services/resources/RealmsResource.java | 11 +- .../resources/RequiredActionsService.java | 2 +- .../services/resources/flows/FormFlows.java | 2 +- .../services/resources/flows/Urls.java | 12 +- .../keycloak/test/ApplicationModelTest.java | 15 +- .../keycloak/testsuite/forms/AccountTest.java | 19 +- .../testsuite/pages/AbstractAccountPage.java | 42 +++ .../pages/{Page.java => AbstractPage.java} | 2 +- .../testsuite/pages/AccountPasswordPage.java | 2 +- .../testsuite/pages/AccountTotpPage.java | 2 +- .../pages/AccountUpdateProfilePage.java | 2 +- .../org/keycloak/testsuite/pages/AppPage.java | 2 +- .../keycloak/testsuite/pages/ErrorPage.java | 3 +- .../testsuite/pages/LoginConfigTotpPage.java | 2 +- .../keycloak/testsuite/pages/LoginPage.java | 2 +- .../pages/LoginPasswordResetPage.java | 2 +- .../pages/LoginPasswordUpdatePage.java | 2 +- .../testsuite/pages/LoginTotpPage.java | 2 +- .../pages/LoginUpdateProfilePage.java | 2 +- .../testsuite/pages/OAuthGrantPage.java | 2 +- .../testsuite/pages/RegisterPage.java | 2 +- .../testsuite/pages/VerifyEmailPage.java | 2 +- .../org/keycloak/testsuite/rule/WebRule.java | 4 +- .../src/test/resources/testrealm.json | 1 + 35 files changed, 369 insertions(+), 143 deletions(-) create mode 100644 testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractAccountPage.java rename testsuite/integration/src/test/java/org/keycloak/testsuite/pages/{Page.java => AbstractPage.java} (97%) diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html index f3d97f8863..8c5fa9c373 100755 --- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html +++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-detail.html @@ -75,6 +75,20 @@ +
+ +
+ + +
+
diff --git a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java index 71b58d7971..0fde58744a 100755 --- a/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java +++ b/core/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java @@ -22,7 +22,14 @@ import java.net.URI; public class JaxrsOAuthClient extends AbstractOAuthClient { protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class); public Response redirect(UriInfo uriInfo, String redirectUri) { + return redirect(uriInfo, redirectUri, null); + } + + public Response redirect(UriInfo uriInfo, String redirectUri, String path) { String state = getStateCode(); + if (path != null) { + state += "#" + path; + } URI url = UriBuilder.fromUri(authUrl) .queryParam("client_id", clientId) @@ -58,7 +65,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient { return uriInfo.getQueryParameters().getFirst("code"); } - public void checkStateCookie(UriInfo uriInfo, HttpHeaders headers) { + public String checkStateCookie(UriInfo uriInfo, HttpHeaders headers) { Cookie stateCookie = headers.getCookies().get(stateCookieName); if (stateCookie == null) throw new BadRequestException("state cookie not set"); String state = uriInfo.getQueryParameters().getFirst("state"); @@ -66,5 +73,10 @@ public class JaxrsOAuthClient extends AbstractOAuthClient { if (!state.equals(stateCookie.getValue())) { throw new BadRequestException("state parameter invalid"); } + if (state.indexOf('#') != -1) { + return state.substring(state.indexOf('#') + 1); + } else { + return null; + } } } diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 14d7d501af..45d47d8beb 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -16,6 +16,7 @@ public class RealmRepresentation { protected Integer accessCodeLifespan; protected Integer accessCodeLifespanUserAction; protected Boolean enabled; + protected Boolean accountManagement; protected Boolean sslNotRequired; protected Boolean cookieLoginAllowed; protected Boolean registrationAllowed; @@ -101,6 +102,14 @@ public class RealmRepresentation { this.enabled = enabled; } + public Boolean isAccountManagement() { + return accountManagement; + } + + public void setAccountManagement(Boolean accountManagement) { + this.accountManagement = accountManagement; + } + public Boolean isSslNotRequired() { return sslNotRequired; } diff --git a/examples/js/testrealm.json b/examples/js/testrealm.json index 2468f48035..38225c618c 100755 --- a/examples/js/testrealm.json +++ b/examples/js/testrealm.json @@ -8,6 +8,7 @@ "sslNotRequired": true, "cookieLoginAllowed": true, "registrationAllowed": true, + "accountManagement": true, "resetPasswordAllowed": true, "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", diff --git a/forms/src/main/java/org/keycloak/forms/UrlBean.java b/forms/src/main/java/org/keycloak/forms/UrlBean.java index f527d38907..8d25c3d969 100755 --- a/forms/src/main/java/org/keycloak/forms/UrlBean.java +++ b/forms/src/main/java/org/keycloak/forms/UrlBean.java @@ -124,6 +124,10 @@ public class UrlBean { return Urls.accountTotpRemove(baseURI, realm.getId()).toString(); } + public String getLogoutUrl() { + return Urls.accountLogout(baseURI, realm.getId()).toString(); + } + public String getLoginPasswordResetUrl() { return Urls.loginPasswordReset(baseURI, realm.getId()).toString(); } diff --git a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl index 18ab4d5b60..19a95ee7fe 100644 --- a/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl +++ b/forms/src/main/resources/META-INF/resources/forms/theme/default/template-main.ftl @@ -54,6 +54,9 @@ Icon: user ${user.firstName!''} ${user.lastName!''} +
  • + Logout +
  • diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java index 52db21f024..b0af34c6ce 100755 --- a/model/api/src/main/java/org/keycloak/models/Constants.java +++ b/model/api/src/main/java/org/keycloak/models/Constants.java @@ -11,4 +11,6 @@ public interface Constants { String APPLICATION_ROLE = "KEYCLOAK_APPLICATION"; String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER"; String WILDCARD_ROLE = "*"; + + String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management"; } 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 925076abc1..1797bc01a9 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -13,6 +13,7 @@ import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.UserModel; +import org.keycloak.services.resources.AccountService; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.SaasService; @@ -61,6 +62,11 @@ public class AuthenticationManager { return createLoginCookie(realm, user, cookieName, cookiePath); } + public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) { + String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE; + String cookiePath = uri.getPath(); + return createLoginCookie(realm, user, cookieName, cookiePath); + } protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) { SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName()); @@ -99,6 +105,11 @@ public class AuthenticationManager { expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath); } + public void expireAccountIdentityCookie(URI uri) { + String cookiePath = uri.getPath(); + expireCookie(AccountService.ACCOUNT_IDENTITY_COOKIE, cookiePath); + } + public void expireCookie(String cookieName, String path) { HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); if (response == null) { @@ -120,6 +131,11 @@ public class AuthenticationManager { return authenticateIdentityCookie(realm, uriInfo, headers, cookieName); } + public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { + String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE; + return authenticateIdentityCookie(realm, uriInfo, headers, cookieName); + } + public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers); if (user != null) return user; diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index af6d4db93a..bae0cc2709 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -90,6 +90,36 @@ public class RealmManager { if (rep.getDefaultRoles() != null) { realm.updateDefaultRoles(rep.getDefaultRoles()); } + + if (rep.isAccountManagement()) { + enableAccountManagement(realm); + } else { + disableAccountManagement(realm); + } + } + + private void enableAccountManagement(RealmModel realm) { + ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + if (application == null) { + application = realm.addApplication(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + + UserCredentialModel password = new UserCredentialModel(); + password.setType(UserCredentialModel.PASSWORD); + password.setValue(UUID.randomUUID().toString()); // just a random password as we'll never access it + + realm.updateCredential(application.getApplicationUser(), password); + + RoleModel applicationRole = realm.getRole(Constants.APPLICATION_ROLE); + realm.grantRole(application.getApplicationUser(), applicationRole); + } + application.setEnabled(true); + } + + private void disableAccountManagement(RealmModel realm) { + ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + if (application != null) { + application.setEnabled(false); // TODO Should we delete the application instead? + } } public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) { @@ -214,6 +244,10 @@ public class RealmManager { } } } + + if (rep.isAccountManagement() != null && rep.isAccountManagement()) { + enableAccountManagement(newRealm); + } } public void createRole(RealmModel newRealm, RoleRepresentation roleRep) { @@ -370,6 +404,9 @@ public class RealmManager { rep.setAccessCodeLifespan(realm.getAccessCodeLifespan()); rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction()); + ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled()); + List defaultRoles = realm.getDefaultRoles(); if (defaultRoles.size() > 0) { String[] d = new String[defaultRoles.size()]; diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java index c3de829e81..96d0c10c2e 100755 --- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java @@ -37,16 +37,20 @@ public class ResourceAdminManager { } protected boolean logoutResource(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) { - LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user); - String token = new TokenManager().encodeToken(realm, adminAction); - Form form = new Form(); - form.param("token", token); String managementUrl = resource.getManagementUrl(); - logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl); - Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form)); - boolean success = response.getStatus() == 204; - response.close(); - return success; + if (managementUrl != null) { + LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), System.currentTimeMillis() / 1000 + 30, resource.getName(), user); + String token = new TokenManager().encodeToken(realm, adminAction); + Form form = new Form(); + form.param("token", token); + logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl); + Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form)); + boolean success = response.getStatus() == 204; + response.close(); + return success; + } else { + return false; + } } } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 7205d6f73c..2a543238a6 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -21,37 +21,35 @@ */ package org.keycloak.services.resources; +import java.net.URI; import java.util.HashSet; import java.util.Set; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; +import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.jose.jws.JWSInput; import org.jboss.resteasy.jose.jws.crypto.RSAProvider; +import org.jboss.resteasy.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; +import org.keycloak.AbstractOAuthClient; +import org.keycloak.jaxrs.JaxrsOAuthClient; +import org.keycloak.models.*; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.email.EmailSender; import org.keycloak.services.managers.AccessCodeEntry; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.messages.Messages; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; import org.keycloak.models.UserModel.RequiredAction; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.FormFlows; +import org.keycloak.services.resources.flows.Pages; +import org.keycloak.services.resources.flows.Urls; import org.keycloak.services.validation.Validation; import org.picketlink.idm.credential.util.TimeBasedOTP; @@ -60,6 +58,10 @@ import org.picketlink.idm.credential.util.TimeBasedOTP; */ public class AccountService { + private static final Logger logger = Logger.getLogger(AccountService.class); + + public static final String ACCOUNT_IDENTITY_COOKIE = "KEYCLOAK_ACCOUNT_IDENTITY"; + private RealmModel realm; @Context @@ -72,37 +74,64 @@ public class AccountService { private UriInfo uriInfo; @Context - protected Providers providers; + private Providers providers; - protected AuthenticationManager authManager = new AuthenticationManager(); + private AuthenticationManager authManager = new AuthenticationManager(); + + private ApplicationModel application; private TokenManager tokenManager; - public AccountService(RealmModel realm, TokenManager tokenManager) { + public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager) { this.realm = realm; + this.application = application; this.tokenManager = tokenManager; } + private Response forwardToPage(String path, String template) { + UserModel user = getUser(false); + if (user != null) { + return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template); + } else { + return login(path); + } + } + + @Path("") + @GET + public Response accountPage() { + return forwardToPage(null, Pages.ACCOUNT); + } + + @Path("social") + @GET + public Response socialPage() { + return forwardToPage("social", Pages.SOCIAL); + } + + @Path("totp") + @GET + public Response totpPage() { + return forwardToPage("totp", Pages.TOTP); + } + + @Path("password") + @GET + public Response passwordPage() { + return forwardToPage("password", Pages.PASSWORD); + } + @Path("access") @GET public Response accessPage() { - UserModel user = getUserFromAuthManager(); - if (user != null) { - return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccess(); - } else { - return Response.status(Status.FORBIDDEN).build(); - } + return forwardToPage("access", Pages.ACCESS); } @Path("") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response processAccountUpdate(final MultivaluedMap formData) { - UserModel user = getUserFromAuthManager(); - if (user == null) { - return Response.status(Status.FORBIDDEN).build(); - } - + UserModel user = getUser(true); user.setFirstName(formData.getFirst("firstName")); user.setLastName(formData.getFirst("lastName")); user.setEmail(formData.getFirst("email")); @@ -113,7 +142,7 @@ public class AccountService { @Path("totp-remove") @GET public Response processTotpRemove() { - UserModel user = getUserFromAuthManager(); + UserModel user = getUser(true); user.setTotp(false); return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS) .setUser(user).forwardToTotp(); @@ -123,31 +152,21 @@ public class AccountService { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response processTotpUpdate(final MultivaluedMap formData) { - UserModel user = getUserFromAuthManager(); - if (user == null) { - return Response.status(Status.FORBIDDEN).build(); - } - - FormFlows forms = Flows.forms(realm, request, uriInfo); + UserModel user = getUser(true); String totp = formData.getFirst("totp"); String totpSecret = formData.getFirst("totpSecret"); - String error = null; - + FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user); if (Validation.isEmpty(totp)) { - error = Messages.MISSING_TOTP; + return forms.setError(Messages.MISSING_TOTP).forwardToTotp(); } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) { - error = Messages.INVALID_TOTP; - } - - if (error != null) { - return forms.setError(error).setUser(user).forwardToTotp(); + return forms.setError(Messages.INVALID_TOTP).forwardToTotp(); } UserCredentialModel credentials = new UserCredentialModel(); credentials.setType(CredentialRepresentation.TOTP); - credentials.setValue(formData.getFirst("totpSecret")); + credentials.setValue(totpSecret); realm.updateCredential(user, credentials); user.setTotp(true); @@ -160,10 +179,7 @@ public class AccountService { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response processPasswordUpdate(final MultivaluedMap formData) { - UserModel user = getUserFromAuthManager(); - if (user == null) { - return Response.status(Status.FORBIDDEN).build(); - } + UserModel user = getUser(true); FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user); @@ -172,18 +188,17 @@ public class AccountService { String passwordConfirm = formData.getFirst("password-confirm"); if (Validation.isEmpty(passwordNew)) { - forms.setError(Messages.MISSING_PASSWORD).forwardToPassword(); + return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword(); } else if (!passwordNew.equals(passwordConfirm)) { - forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword(); + return forms.setError(Messages.INVALID_PASSWORD_CONFIRM).forwardToPassword(); } if (Validation.isEmpty(password)) { - forms.setError(Messages.MISSING_PASSWORD).forwardToPassword(); + return forms.setError(Messages.MISSING_PASSWORD).forwardToPassword(); } else if (!realm.validatePassword(user, password)) { - forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword(); + return forms.setError(Messages.INVALID_PASSWORD_EXISTING).forwardToPassword(); } - UserCredentialModel credentials = new UserCredentialModel(); credentials.setType(CredentialRepresentation.PASSWORD); credentials.setValue(passwordNew); @@ -193,50 +208,112 @@ public class AccountService { return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword(); } - @Path("") + @Path("login-redirect") @GET - public Response accountPage() { - UserModel user = getUserFromAuthManager(); - if (user != null) { - return Flows.forms(realm, request, uriInfo).setUser(user).forwardToAccount(); - } else { - return Response.status(Status.FORBIDDEN).build(); + public Response loginRedirect(@QueryParam("code") String code, + @QueryParam("state") String state, + @QueryParam("error") String error, + @Context HttpHeaders headers) { + try { + if (error != null) { + logger.debug("error from oauth"); + throw new ForbiddenException("error"); + } + if (!realm.isEnabled()) { + logger.debug("realm not enabled"); + throw new ForbiddenException(); + } + UserModel client = application.getApplicationUser(); + if (!client.isEnabled() || !application.isEnabled()) { + logger.debug("account management app not enabled"); + throw new ForbiddenException(); + } + if (code == null) { + logger.debug("code not specified"); + throw new BadRequestException(); + } + if (state == null) { + logger.debug("state not specified"); + throw new BadRequestException(); + } + String path = new JaxrsOAuthClient().checkStateCookie(uriInfo, headers); + + JWSInput input = new JWSInput(code, providers); + boolean verifiedCode = false; + try { + verifiedCode = RSAProvider.verify(input, realm.getPublicKey()); + } catch (Exception ignored) { + logger.debug("Failed to verify signature", ignored); + } + if (!verifiedCode) { + logger.debug("unverified access code"); + throw new BadRequestException(); + } + String key = input.readContent(String.class); + AccessCodeEntry accessCode = tokenManager.pullAccessCode(key); + if (accessCode == null) { + logger.debug("bad access code"); + throw new BadRequestException(); + } + if (accessCode.isExpired()) { + logger.debug("access code expired"); + throw new BadRequestException(); + } + if (!accessCode.getToken().isActive()) { + logger.debug("access token expired"); + throw new BadRequestException(); + } + if (!accessCode.getRealm().getId().equals(realm.getId())) { + logger.debug("bad realm"); + throw new BadRequestException(); + + } + if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) { + logger.debug("bad client"); + throw new BadRequestException(); + } + + UriBuilder redirectBuilder = Urls.accountBase(uriInfo.getBaseUri()); + if (path != null) { + redirectBuilder.path(path); + } + URI redirectUri = redirectBuilder.build(realm.getId()); + + NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId())); + return Response.status(302).cookie(cookie).location(redirectUri).build(); + } finally { + authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath()); } } - @Path("social") + @Path("logout") @GET - public Response socialPage() { - UserModel user = getUserFromAuthManager(); - if (user != null) { - return Flows.forms(realm, request, uriInfo).setUser(user).forwardToSocial(); - } else { - return Response.status(Status.FORBIDDEN).build(); - } + public Response logout() { + // TODO Should use single-sign out via TokenService + URI baseUri = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()); + authManager.expireIdentityCookie(realm, uriInfo); + authManager.expireAccountIdentityCookie(baseUri); + return Response.status(302).location(baseUri).build(); } - @Path("totp") - @GET - public Response totpPage() { - UserModel user = getUserFromAuthManager(); - if (user != null) { - return Flows.forms(realm, request, uriInfo).setUser(user).forwardToTotp(); - } else { - return Response.status(Status.FORBIDDEN).build(); - } + private Response login(String path) { + JaxrsOAuthClient oauth = new JaxrsOAuthClient(); + String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString(); + oauth.setAuthUrl(authUrl); + + oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + + URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId()); + + oauth.setStateCookiePath(accountUri.getPath()); + return oauth.redirect(uriInfo, accountUri.toString(), path); } - @Path("password") - @GET - public Response passwordPage() { - UserModel user = getUserFromAuthManager(); - if (user == null) { - return Response.status(Status.FORBIDDEN).build(); + private UserModel getUser(boolean required) { + UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers); + if (user == null && required) { + throw new ForbiddenException(); } - return Flows.forms(realm, request, uriInfo).setUser(user).forwardToPassword(); - } - - private UserModel getUserFromAuthManager() { - return authManager.authenticateIdentityCookie(realm, uriInfo, headers); + return user; } } 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 9914622862..54fd5a5b33 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -1,6 +1,8 @@ package org.keycloak.services.resources; import org.jboss.resteasy.logging.Logger; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.Constants; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.models.KeycloakSession; @@ -66,7 +68,14 @@ public class RealmsResource { logger.debug("realm not found"); throw new NotFoundException(); } - AccountService accountService = new AccountService(realm, tokenManager); + + ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION); + if (application == null || !application.isEnabled()) { + logger.debug("account management not enabled"); + throw new NotFoundException(); + } + + AccountService accountService = new AccountService(realm, application, tokenManager); resourceContext.initResource(accountService); return accountService; } diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java index 67a241e4e6..eeed370453 100755 --- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java @@ -127,7 +127,7 @@ public class RequiredActionsService { UserCredentialModel credentials = new UserCredentialModel(); credentials.setType(CredentialRepresentation.TOTP); - credentials.setValue(formData.getFirst("totpSecret")); + credentials.setValue(totpSecret); realm.updateCredential(user, credentials); user.setTotp(true); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java index 2aebb4e84b..fe924fbfe0 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java @@ -141,7 +141,7 @@ public class FormFlows { return Response.status(200).entity("form provider not found").build(); } - private Response forwardToForm(String template) { + public Response forwardToForm(String template) { FormService.FormServiceDataBean formDataBean = new FormService.FormServiceDataBean(realm, userModel, formData, error); formDataBean.setMessageType(messageType); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java index 200b746ea3..b9f457aa40 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java @@ -35,12 +35,16 @@ public class Urls { return accountBase(baseUri).path(AccountService.class, "accessPage").build(realmId); } - private static UriBuilder accountBase(URI baseUri) { + public static UriBuilder accountBase(URI baseUri) { return realmBase(baseUri).path(RealmsResource.class, "getAccountService"); } public static URI accountPage(URI baseUri, String realmId) { - return accountBase(baseUri).path(AccountService.class, "accountPage").build(realmId); + return accountPageBuilder(baseUri).build(realmId); + } + + public static UriBuilder accountPageBuilder(URI baseUri) { + return accountBase(baseUri).path(AccountService.class, "accountPage"); } public static URI accountPasswordPage(URI baseUri, String realmId) { @@ -59,6 +63,10 @@ public class Urls { return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId); } + public static URI accountLogout(URI baseUri, String realmId) { + return accountBase(baseUri).path(AccountService.class, "logout").build(realmId); + } + public static URI loginActionUpdatePassword(URI baseUri, String realmId) { return requiredActionsBase(baseUri).path(RequiredActionsService.class, "updatePassword").build(realmId); } diff --git a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java index cc7068b1f4..57c3ba5004 100644 --- a/services/src/test/java/org/keycloak/test/ApplicationModelTest.java +++ b/services/src/test/java/org/keycloak/test/ApplicationModelTest.java @@ -1,23 +1,20 @@ package org.keycloak.test; -import java.util.Iterator; -import java.util.List; - import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.keycloak.models.ApplicationModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.representations.idm.ApplicationRepresentation; import org.keycloak.services.managers.ApplicationManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; +import java.util.Iterator; +import java.util.List; + +import static org.junit.Assert.assertNotNull; + /** * @author Stian Thorgersen */ diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java index 3d62d55ed5..4f8e75ada3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AccountTest.java @@ -21,11 +21,7 @@ */ package org.keycloak.testsuite.forms; -import org.junit.After; -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; +import org.junit.*; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.models.RealmModel; @@ -97,13 +93,12 @@ public class AccountTest { @Test public void changePassword() { - loginPage.open(); + changePasswordPage.open(); loginPage.login("test-user@localhost", "password"); - changePasswordPage.open(); changePasswordPage.changePassword("password", "new-password", "new-password"); - oauth.openLogout(); + changePasswordPage.logout(); loginPage.open(); loginPage.login("test-user@localhost", "password"); @@ -118,10 +113,8 @@ public class AccountTest { @Test public void changeProfile() { - loginPage.open(); - loginPage.login("test-user@localhost", "password"); - profilePage.open(); + loginPage.login("test-user@localhost", "password"); Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("", profilePage.getLastName()); @@ -136,10 +129,8 @@ public class AccountTest { @Test public void setupTotp() { - loginPage.open(); - loginPage.login("test-user@localhost", "password"); - totpPage.open(); + loginPage.login("test-user@localhost", "password"); Assert.assertTrue(totpPage.isCurrent()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractAccountPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractAccountPage.java new file mode 100644 index 0000000000..9860b14078 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractAccountPage.java @@ -0,0 +1,42 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testsuite.pages; + +import org.junit.Assert; +import org.keycloak.testsuite.rule.WebResource; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Stian Thorgersen + */ +public abstract class AbstractAccountPage extends AbstractPage { + + @FindBy(linkText = "Logout") + private WebElement logoutLink; + + public void logout() { + logoutLink.click(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/Page.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java similarity index 97% rename from testsuite/integration/src/test/java/org/keycloak/testsuite/pages/Page.java rename to testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java index d38e2acfc1..1183ece12a 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/Page.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AbstractPage.java @@ -28,7 +28,7 @@ import org.openqa.selenium.WebDriver; /** * @author Stian Thorgersen */ -public abstract class Page { +public abstract class AbstractPage { @WebResource protected WebDriver driver; 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 b53e88ff9a..0a94649c73 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java @@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class AccountPasswordPage extends Page { +public class AccountPasswordPage extends AbstractAccountPage { private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/password"; 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 11cc502362..171e6efa4f 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountTotpPage.java @@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class AccountTotpPage extends Page { +public class AccountTotpPage extends AbstractAccountPage { private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/totp"; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java index c890f2ff72..7c99e1bc53 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java @@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class AccountUpdateProfilePage extends Page { +public class AccountUpdateProfilePage extends AbstractAccountPage { private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account"; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java index 4270640740..3faa197818 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AppPage.java @@ -25,7 +25,7 @@ package org.keycloak.testsuite.pages; /** * @author Stian Thorgersen */ -public class AppPage extends Page { +public class AppPage extends AbstractPage { private String baseUrl = "http://localhost:8081/app"; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java index bfe3201984..cc06da5d3e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java @@ -23,14 +23,13 @@ package org.keycloak.testsuite.pages; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.rule.WebResource; -import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class ErrorPage extends Page { +public class ErrorPage extends AbstractPage { @WebResource protected OAuthClient oauth; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java index f2e23003cc..5b1613ad0c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginConfigTotpPage extends Page { +public class LoginConfigTotpPage extends AbstractPage { @FindBy(id = "totpSecret") private WebElement totpSecret; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java index d099690763..28e87cf8cb 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java @@ -30,7 +30,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginPage extends Page { +public class LoginPage extends AbstractPage { @WebResource protected OAuthClient oauth; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java index 45fc30a512..fc62c3ca19 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordResetPage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginPasswordResetPage extends Page { +public class LoginPasswordResetPage extends AbstractPage { @FindBy(id = "username") private WebElement usernameInput; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java index 100b891c1f..be5df8c0fb 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginPasswordUpdatePage extends Page { +public class LoginPasswordUpdatePage extends AbstractPage { @FindBy(id = "password-new") private WebElement newPasswordInput; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java index 67b3801c5f..2a7ed578ca 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginTotpPage.java @@ -28,7 +28,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginTotpPage extends Page { +public class LoginTotpPage extends AbstractPage { @FindBy(id = "totp") private WebElement totpInput; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java index 9a4b45465c..d25959d6e1 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class LoginUpdateProfilePage extends Page { +public class LoginUpdateProfilePage extends AbstractPage { @FindBy(id = "firstName") private WebElement firstNameInput; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java index 8749a918ce..7fdbe068f1 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/OAuthGrantPage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class OAuthGrantPage extends Page { +public class OAuthGrantPage extends AbstractPage { @FindBy(css = "input[name=\"accept\"]") private WebElement acceptButton; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java index 7ff4d7f97e..292a462aa4 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java @@ -27,7 +27,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Stian Thorgersen */ -public class RegisterPage extends Page { +public class RegisterPage extends AbstractPage { @FindBy(id = "firstName") private WebElement firstNameInput; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java index 5e8c24c65b..cfcfbb4fdb 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/VerifyEmailPage.java @@ -29,7 +29,7 @@ import org.openqa.selenium.support.FindBy; /** * @author Viliam Rockai */ -public class VerifyEmailPage extends Page { +public class VerifyEmailPage extends AbstractPage { @WebResource protected OAuthClient oauth; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java index d9f789d578..88301c5417 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/WebRule.java @@ -25,7 +25,7 @@ import java.lang.reflect.Field; import org.junit.rules.ExternalResource; import org.keycloak.testsuite.OAuthClient; -import org.keycloak.testsuite.pages.Page; +import org.keycloak.testsuite.pages.AbstractPage; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.firefox.FirefoxDriver; @@ -78,7 +78,7 @@ public class WebRule extends ExternalResource { Class type = f.getType(); if (type.equals(WebDriver.class)) { set(f, o, driver); - } else if (Page.class.isAssignableFrom(type)) { + } else if (AbstractPage.class.isAssignableFrom(type)) { set(f, o, getPage(f.getType())); } else if (type.equals(OAuthClient.class)) { set(f, o, oauth); diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json index 941c5db836..161f11e9c6 100755 --- a/testsuite/integration/src/test/resources/testrealm.json +++ b/testsuite/integration/src/test/resources/testrealm.json @@ -8,6 +8,7 @@ "sslNotRequired": true, "cookieLoginAllowed": true, "registrationAllowed": true, + "accountManagement": true, "resetPasswordAllowed": true, "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",