diff --git a/core/src/main/java/org/keycloak/util/Time.java b/core/src/main/java/org/keycloak/util/Time.java index 3b3aef4b31..a7dc0fbd09 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 Stian Thorgersen */ @@ -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 f04a28373b..824165b37b 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 5a62fecbea..fde532463d 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 events); + Account setSessions(List 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 ceeca40564..dc7e3c0f06 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 fb30dacac4..a2e7979f82 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 events; + private List 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 { @@ -178,6 +185,12 @@ public class FreeMarkerAccount implements Account { return this; } + @Override + public Account setSessions(List sessions) { + this.sessions = sessions; + return this; + } + @Override public Account setFeatures(boolean social, boolean audit, boolean passwordUpdateSupported) { this.social = social; 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 f9d051cb2f..80fc179d7d 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/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 0000000000..4db66ddb64 --- /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 Stian Thorgersen + */ +public class SessionsBean { + + private List events; + + public SessionsBean(List sessions) { + this.events = new LinkedList(); + for (UserSessionModel session : sessions) { + this.events.add(new UserSessionBean(session)); + } + } + + public List 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 6467146dc3..fce032fc0b 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/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 0000000000..e424e51c12 --- /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> + +
+
+

Sessions

+
+
+ + + + + + + + + + + + <#list sessions.sessions as session> + + + + + + + + +
IPStartedExpires
${session.ipAddress}${session.started?datetime}${session.expires?datetime}
+ + Logout all sessions + + \ 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 883d8fbb26..49040df65b 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>
  • Password
  • Authenticator
  • <#if features.social>
  • Social
  • +
  • Sessions
  • <#if features.log>
  • Log
  • 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 e3f41eb8ef..ac91fc768d 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 getUserSessions(UserModel user); + void removeUserSession(UserSessionModel session); void removeUserSessions(UserModel user); 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 d9c3784a63..d462e65ac8 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; @@ -1405,6 +1406,15 @@ public class RealmAdapter implements RealmModel { return entity != null ? new UserSessionAdapter(entity) : null; } + @Override + public List getUserSessions(UserModel user) { + List sessions = new LinkedList(); + 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/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 5d4fde74a2..83db701163 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/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 cb0e8d7265..97917401e0 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; @@ -1375,6 +1376,16 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } } + @Override + public List getUserSessions(UserModel user) { + DBObject query = new BasicDBObject("user", user.getId()); + List sessions = new LinkedList(); + 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 6c1142b0e2..35c70f67e2 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 435c34b916..961fbcb65f 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 d3c5859c5c..167ca7ee19 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; @@ -130,6 +131,9 @@ public class AccountTest { @WebResource protected AccountLogPage logPage; + @WebResource + protected AccountSessionsPage sessionsPage; + @WebResource protected ErrorPage errorPage; @@ -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> 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 ab854332af..846b526326 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 d621c718f7..0dc027bc81 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 0000000000..5467a48aff --- /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 Stian Thorgersen + */ +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> getSessions() { + List> table = new LinkedList>(); + for (WebElement r : driver.findElements(By.tagName("tr"))) { + List row = new LinkedList(); + for (WebElement col : r.findElements(By.tagName("td"))) { + row.add(col.getText()); + } + table.add(row); + } + table.remove(0); + return table; + } + +}