keycloak-memoizeit
Changes
examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java 3(+2 -1)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java 15(+14 -1)
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java 50(+50 -0)
Details
diff --git a/core/src/main/java/org/keycloak/util/Time.java b/core/src/main/java/org/keycloak/util/Time.java
index 3b3aef4..a7dc0fb 100644
--- a/core/src/main/java/org/keycloak/util/Time.java
+++ b/core/src/main/java/org/keycloak/util/Time.java
@@ -1,5 +1,7 @@
package org.keycloak.util;
+import java.util.Date;
+
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@@ -9,4 +11,8 @@ public class Time {
return (int) (System.currentTimeMillis() / 1000);
}
+ public static Date toDate(int time) {
+ return new Date(((long) time ) * 1000);
+ }
+
}
diff --git a/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java b/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
index f04a283..824165b 100644
--- a/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
+++ b/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
@@ -7,6 +7,7 @@ import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.installed.KeycloakInstalled;
+import org.keycloak.util.Time;
import java.io.BufferedReader;
import java.io.IOException;
@@ -65,7 +66,7 @@ public class CustomerCli {
System.out.println(mapper.writeValueAsString(keycloak.getIdToken()));
} else if (s.equals("refresh")) {
keycloak.refreshToken();
- System.out.println("Token refreshed: expires at " + new Date(keycloak.getToken().getExpiration() * 1000));
+ System.out.println("Token refreshed: expires at " + Time.toDate(keycloak.getToken().getExpiration()));
} else if (s.equals("exit")) {
System.exit(0);
} else {
diff --git a/forms/account-api/src/main/java/org/keycloak/account/Account.java b/forms/account-api/src/main/java/org/keycloak/account/Account.java
index 5a62fec..fde5324 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/Account.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/Account.java
@@ -3,6 +3,7 @@ package org.keycloak.account;
import org.keycloak.audit.Event;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@@ -31,5 +32,7 @@ public interface Account {
Account setEvents(List<Event> events);
+ Account setSessions(List<UserSessionModel> sessions);
+
Account setFeatures(boolean social, boolean audit, boolean passwordUpdateSupported);
}
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
index ceeca40..dc7e3c0 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
@@ -5,6 +5,6 @@ package org.keycloak.account;
*/
public enum AccountPages {
- ACCOUNT, PASSWORD, TOTP, SOCIAL, LOG;
+ ACCOUNT, PASSWORD, TOTP, SOCIAL, LOG, SESSIONS;
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
index fb30dac..a2e7979 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -9,6 +9,7 @@ import org.keycloak.account.freemarker.model.FeaturesBean;
import org.keycloak.account.freemarker.model.LogBean;
import org.keycloak.account.freemarker.model.MessageBean;
import org.keycloak.account.freemarker.model.ReferrerBean;
+import org.keycloak.account.freemarker.model.SessionsBean;
import org.keycloak.account.freemarker.model.TotpBean;
import org.keycloak.account.freemarker.model.UrlBean;
import org.keycloak.audit.Event;
@@ -19,6 +20,7 @@ import org.keycloak.freemarker.ThemeLoader;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -43,6 +45,7 @@ public class FreeMarkerAccount implements Account {
private RealmModel realm;
private String[] referrer;
private List<Event> events;
+ private List<UserSessionModel> sessions;
private boolean social;
private boolean audit;
private boolean passwordUpdateSupported;
@@ -100,7 +103,7 @@ public class FreeMarkerAccount implements Account {
attributes.put("referrer", new ReferrerBean(referrer));
}
- attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri));
+ attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri()));
attributes.put("features", new FeaturesBean(social, audit, passwordUpdateSupported));
@@ -116,6 +119,10 @@ public class FreeMarkerAccount implements Account {
break;
case LOG:
attributes.put("log", new LogBean(events));
+ break;
+ case SESSIONS:
+ attributes.put("sessions", new SessionsBean(sessions));
+ break;
}
try {
@@ -179,6 +186,12 @@ public class FreeMarkerAccount implements Account {
}
@Override
+ public Account setSessions(List<UserSessionModel> sessions) {
+ this.sessions = sessions;
+ return this;
+ }
+
+ @Override
public Account setFeatures(boolean social, boolean audit, boolean passwordUpdateSupported) {
this.social = social;
this.audit = audit;
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
new file mode 100644
index 0000000..4db66dd
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/SessionsBean.java
@@ -0,0 +1,50 @@
+package org.keycloak.account.freemarker.model;
+
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.util.Time;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionsBean {
+
+ private List<UserSessionBean> events;
+
+ public SessionsBean(List<UserSessionModel> sessions) {
+ this.events = new LinkedList<UserSessionBean>();
+ for (UserSessionModel session : sessions) {
+ this.events.add(new UserSessionBean(session));
+ }
+ }
+
+ public List<UserSessionBean> getSessions() {
+ return events;
+ }
+
+ public static class UserSessionBean {
+
+ private UserSessionModel session;
+
+ public UserSessionBean(UserSessionModel session) {
+ this.session = session;
+ }
+
+ public String getIpAddress() {
+ return session.getIpAddress();
+ }
+
+ public Date getStarted() {
+ return Time.toDate(session.getStarted());
+ }
+
+ public Date getExpires() {
+ return Time.toDate(session.getExpires());
+ }
+
+ }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
index 6467146..fce032f 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
@@ -2,6 +2,7 @@ package org.keycloak.account.freemarker.model;
import org.keycloak.freemarker.Theme;
import org.keycloak.models.RealmModel;
+import org.keycloak.services.resources.TokenService;
import org.keycloak.services.resources.flows.Urls;
import java.net.URI;
@@ -15,12 +16,14 @@ public class UrlBean {
private Theme theme;
private URI baseURI;
private URI baseQueryURI;
+ private URI currentURI;
- public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI baseQueryURI) {
+ public UrlBean(RealmModel realm, Theme theme, URI baseURI, URI baseQueryURI, URI currentURI) {
this.realm = realm.getName();
this.theme = theme;
this.baseURI = baseURI;
this.baseQueryURI = baseQueryURI;
+ this.currentURI = currentURI;
}
public String getAccessUrl() {
@@ -47,12 +50,20 @@ public class UrlBean {
return Urls.accountLogPage(baseQueryURI, realm).toString();
}
+ public String getSessionsUrl() {
+ return Urls.accountSessionsPage(baseQueryURI, realm).toString();
+ }
+
+ public String getSessionsLogoutUrl() {
+ return Urls.accountSessionsLogoutPage(baseQueryURI, realm).toString();
+ }
+
public String getTotpRemoveUrl() {
return Urls.accountTotpRemove(baseQueryURI, realm).toString();
}
public String getLogoutUrl() {
- return Urls.accountLogout(baseQueryURI, realm).toString();
+ return Urls.accountLogout(baseQueryURI, currentURI, realm).toString();
}
public String getResourcesPath() {
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
index f9d051c..80fc179 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
@@ -19,6 +19,8 @@ public class Templates {
return "social.ftl";
case LOG:
return "log.ftl";
+ case SESSIONS:
+ return "sessions.ftl";
default:
throw new IllegalArgumentException();
}
diff --git a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
new file mode 100644
index 0000000..e424e51
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
@@ -0,0 +1,33 @@
+<#import "template.ftl" as layout>
+<@layout.mainLayout active='sessions' bodyClass='sessions'; section>
+
+ <div class="row">
+ <div class="col-md-10">
+ <h2>Sessions</h2>
+ </div>
+ </div>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <td>IP</td>
+ <td>Started</td>
+ <td>Expires</td>
+ </tr>
+ </thead>
+
+ <tbody>
+ <#list sessions.sessions as session>
+ <tr>
+ <td>${session.ipAddress}</td>
+ <td>${session.started?datetime}</td>
+ <td>${session.expires?datetime}</td>
+ </tr>
+ </#list>
+ </tbody>
+
+ </table>
+
+ <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">Logout all sessions</a>
+
+</@layout.mainLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
index 883d8fb..49040df 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
@@ -43,6 +43,7 @@
<#if features.passwordUpdateSupported><li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li></#if>
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
<#if features.social><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
+ <li class="<#if active=='sessions'>active</#if>"><a href="${url.sessionsUrl}">Sessions</a></li>
<#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">Log</a></li></#if>
</ul>
</div>
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index e3f41eb..ac91fc7 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -257,6 +257,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
UserSessionModel getUserSession(String id);
+ List<UserSessionModel> getUserSessions(UserModel user);
+
void removeUserSession(UserSessionModel session);
void removeUserSessions(UserModel user);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
index 5d4fde7..83db701 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserSessionEntity.java
@@ -15,6 +15,7 @@ import javax.persistence.NamedQuery;
*/
@Entity
@NamedQueries({
+ @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.user = :user"),
@NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.user = :user"),
@NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.expires < :currentTime")
})
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index d9c3784..d462e65 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -49,6 +49,7 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1406,6 +1407,15 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public List<UserSessionModel> getUserSessions(UserModel user) {
+ List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
+ for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class).setParameter("user", ((UserAdapter) user).getUser()).getResultList()) {
+ sessions.add(new UserSessionAdapter(e));
+ }
+ return sessions;
+ }
+
+ @Override
public void removeUserSession(UserSessionModel session) {
em.remove(((UserSessionAdapter) session).getEntity());
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index cb0e8d7..9791740 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -45,6 +45,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -1376,6 +1377,16 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public List<UserSessionModel> getUserSessions(UserModel user) {
+ DBObject query = new BasicDBObject("user", user.getId());
+ List<UserSessionModel> sessions = new LinkedList<UserSessionModel>();
+ for (MongoUserSessionEntity e : getMongoStore().loadEntities(MongoUserSessionEntity.class, query, invocationContext)) {
+ sessions.add(new UserSessionAdapter(e, this, invocationContext));
+ }
+ return sessions;
+ }
+
+ @Override
public void removeUserSession(UserSessionModel session) {
getMongoStore().removeEntity(((UserSessionAdapter) session).getEntity(), invocationContext);
}
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 6c1142b..35c70f6 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -241,10 +241,6 @@ public class AccountService {
return forwardToPage("social", AccountPages.SOCIAL);
}
- public static UriBuilder logUrl(UriBuilder base) {
- return RealmsResource.accountUrl(base).path(AccountService.class, "logPage");
- }
-
@Path("log")
@GET
public Response logPage() {
@@ -269,6 +265,15 @@ public class AccountService {
return forwardToPage("log", AccountPages.LOG);
}
+ @Path("sessions")
+ @GET
+ public Response sessionsPage() {
+ if (auth != null) {
+ account.setSessions(realm.getUserSessions(auth.getUser()));
+ }
+ return forwardToPage("sessions", AccountPages.SESSIONS);
+ }
+
@Path("/")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@@ -314,6 +319,18 @@ public class AccountService {
return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
}
+
+ @Path("sessions-logout")
+ @GET
+ public Response processSessionsLogout() {
+ require(AccountRoles.MANAGE_ACCOUNT);
+
+ UserModel user = auth.getUser();
+ realm.removeUserSessions(user);
+
+ return Response.seeOther(Urls.accountSessionsPage(uriInfo.getBaseUri(), realm.getName())).build();
+ }
+
@Path("totp")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@@ -493,16 +510,6 @@ public class AccountService {
}
}
- @Path("logout")
- @GET
- public Response logout() {
- URI redirect = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName());
-
- return Response.status(302).location(
- TokenService.logoutUrl(uriInfo).queryParam("redirect_uri", redirect.toString()).build(realm.getName())
- ).build();
- }
-
private Response login(String path) {
OAuthRedirect oauth = new OAuthRedirect();
String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getName()).toString();
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 435c34b..961fbcb 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
@@ -76,8 +76,16 @@ public class Urls {
return accountBase(baseUri).path(AccountService.class, "logPage").build(realmId);
}
- public static URI accountLogout(URI baseUri, String realmId) {
- return accountBase(baseUri).path(AccountService.class, "logout").build(realmId);
+ public static URI accountSessionsPage(URI baseUri, String realmId) {
+ return accountBase(baseUri).path(AccountService.class, "sessionsPage").build(realmId);
+ }
+
+ public static URI accountSessionsLogoutPage(URI baseUri, String realmId) {
+ return accountBase(baseUri).path(AccountService.class, "processSessionsLogout").build(realmId);
+ }
+
+ public static URI accountLogout(URI baseUri, URI redirectUri, String realmId) {
+ return realmLogout(baseUri).queryParam("redirect_uri", redirectUri).build(realmId);
}
public static URI loginActionUpdatePassword(URI baseUri, String realmId) {
@@ -128,6 +136,10 @@ public class Urls {
return tokenBase(baseUri).path(TokenService.class, "loginPage").build(realmId);
}
+ public static UriBuilder realmLogout(URI baseUri) {
+ return tokenBase(baseUri).path(TokenService.class, "logout");
+ }
+
public static URI realmRegisterAction(URI baseUri, String realmId) {
return tokenBase(baseUri).path(TokenService.class, "processRegister").build(realmId);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index d3c5859..167ca7e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -48,6 +48,7 @@ import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.Retry;
import org.keycloak.testsuite.pages.AccountLogPage;
import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountSessionsPage;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
@@ -131,6 +132,9 @@ public class AccountTest {
protected AccountLogPage logPage;
@WebResource
+ protected AccountSessionsPage sessionsPage;
+
+ @WebResource
protected ErrorPage errorPage;
private TimeBasedOTP totp = new TimeBasedOTP();
@@ -212,7 +216,7 @@ public class AccountTest {
changePasswordPage.logout();
- events.expectLogout(sessionId).detail(Details.REDIRECT_URI, ACCOUNT_URL).assertEvent();
+ events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AccountPasswordPage.PATH).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "password");
@@ -414,4 +418,41 @@ public class AccountTest {
}
}
+ @Test
+ public void sessions() {
+ loginPage.open();
+ loginPage.clickRegister();
+
+ registerPage.register("view", "sessions", "view-sessions@localhost", "view-sessions", "password", "password");
+
+ Event registerEvent = events.expectRegister("view-sessions", "view-sessions@localhost").assertEvent();
+ String userId = registerEvent.getUserId();
+
+ events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
+
+ sessionsPage.open();
+
+ Assert.assertTrue(sessionsPage.isCurrent());
+
+ List<List<String>> sessions = sessionsPage.getSessions();
+ Assert.assertEquals(1, sessions.size());
+ Assert.assertEquals("127.0.0.1", sessions.get(0).get(0));
+
+ // Create second session
+ WebDriver driver2 = WebRule.createWebDriver();
+ OAuthClient oauth2 = new OAuthClient(driver2);
+ oauth2.doLogin("view-sessions", "password");
+
+ Event login2Event = events.expectLogin().user(userId).detail(Details.USERNAME, "view-sessions").assertEvent();
+
+ sessionsPage.open();
+ sessions = sessionsPage.getSessions();
+ Assert.assertEquals(2, sessions.size());
+
+ sessionsPage.logoutAll();
+
+ events.expectLogout(registerEvent.getSessionId());
+ events.expectLogout(login2Event.getSessionId());
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
index ab85433..846b526 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
@@ -21,11 +21,10 @@
*/
package org.keycloak.testsuite.pages;
-import org.keycloak.services.resources.AccountService;
+import org.keycloak.services.resources.flows.Urls;
import org.keycloak.testsuite.Constants;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
import javax.ws.rs.core.UriBuilder;
import java.util.LinkedList;
@@ -36,7 +35,7 @@ import java.util.List;
*/
public class AccountLogPage extends AbstractAccountPage {
- private static String PATH = AccountService.logUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
+ private static String PATH = Urls.accountLogPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), "test").toString();
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/log");
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 d621c71..0dc027b 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
@@ -33,7 +33,7 @@ import javax.ws.rs.core.UriBuilder;
*/
public class AccountPasswordPage extends AbstractAccountPage {
- private static String PATH = AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
+ public static String PATH = AccountService.passwordUrl(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT)).build("test").toString();
@FindBy(id = "password")
private WebElement passwordInput;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountSessionsPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountSessionsPage.java
new file mode 100755
index 0000000..5467a48
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountSessionsPage.java
@@ -0,0 +1,69 @@
+/*
+ * 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.keycloak.services.resources.flows.Urls;
+import org.keycloak.testsuite.Constants;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import javax.ws.rs.core.UriBuilder;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountSessionsPage extends AbstractAccountPage {
+
+ private static String PATH = Urls.accountSessionsPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), "test").toString();
+
+ @FindBy(id = "logout-all-sessions")
+ private WebElement logoutAllLink;
+
+ public boolean isCurrent() {
+ return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/sessions");
+ }
+
+ public void open() {
+ driver.navigate().to(PATH);
+ }
+
+ public void logoutAll() {
+ logoutAllLink.click();
+ }
+
+ public List<List<String>> getSessions() {
+ List<List<String>> table = new LinkedList<List<String>>();
+ for (WebElement r : driver.findElements(By.tagName("tr"))) {
+ List<String> row = new LinkedList<String>();
+ for (WebElement col : r.findElements(By.tagName("td"))) {
+ row.add(col.getText());
+ }
+ table.add(row);
+ }
+ table.remove(0);
+ return table;
+ }
+
+}