keycloak-aplcache

Added audit log to account mngmt

4/3/2014 12:27:22 PM

Changes

Details

diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
index 9ea67b9..5c75e63 100644
--- a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
@@ -62,7 +62,7 @@ public class MongoEventQuery implements EventQuery {
 
     @Override
     public List<Event> getResultList() {
-        DBCursor cur = audit.find(query);
+        DBCursor cur = audit.find(query).sort(new BasicDBObject("time", -1));
         if (firstResult != null) {
             cur.skip(firstResult);
         }
diff --git a/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
index 217dea4..c3ebdaa 100644
--- a/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
+++ b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
@@ -46,10 +46,13 @@ public abstract class AbstractAuditProviderTest {
 
     @Test
     public void query() {
+        long oldest = System.currentTimeMillis() - 30000;
+        long newest = System.currentTimeMillis() + 30000;
+
         provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
-        provider.onEvent(create("event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(newest, "event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
         provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
-        provider.onEvent(create("event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(oldest, "event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
         provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
 
         provider.close();
@@ -65,6 +68,9 @@ public abstract class AbstractAuditProviderTest {
 
         Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
         Assert.assertEquals(1, provider.createQuery().firstResult(4).getResultList().size());
+
+        Assert.assertEquals(newest, provider.createQuery().maxResults(1).getResultList().get(0).getTime());
+        Assert.assertEquals(oldest, provider.createQuery().firstResult(4).maxResults(1).getResultList().get(0).getTime());
     }
 
     @Test
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 3791611..f92b161 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
@@ -13,22 +13,23 @@ import java.util.List;
  */
 public interface Account {
 
-    public Response createResponse(AccountPages page);
+    Response createResponse(AccountPages page);
 
-    public Account setError(String message);
+    Account setError(String message);
 
-    public Account setSuccess(String message);
+    Account setSuccess(String message);
 
-    public Account setWarning(String message);
+    Account setWarning(String message);
 
-    public Account setUser(UserModel user);
+    Account setUser(UserModel user);
 
-    public Account setStatus(Response.Status status);
+    Account setStatus(Response.Status status);
 
-    public Account setRealm(RealmModel realm);
+    Account setRealm(RealmModel realm);
 
-    public Account setReferrer(String[] referrer);
+    Account setReferrer(String[] referrer);
 
-    public Account setEvents(List<Event> events);
+    Account setEvents(List<Event> events);
 
+    Account setFeatures(boolean social, boolean audit);
 }
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 6074ad1..53f43d4 100644
--- 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
@@ -5,6 +5,7 @@ import org.keycloak.account.Account;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.freemarker.model.AccountBean;
 import org.keycloak.account.freemarker.model.AccountSocialBean;
+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;
@@ -41,6 +42,8 @@ public class FreeMarkerAccount implements Account {
     private RealmModel realm;
     private String[] referrer;
     private List<Event> events;
+    private boolean social;
+    private boolean audit;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -92,9 +95,7 @@ public class FreeMarkerAccount implements Account {
 
         attributes.put("url", new UrlBean(realm, theme, baseUri));
 
-        if (realm.isSocial()) {
-            attributes.put("isSocialRealm", true);
-        }
+        attributes.put("features", new FeaturesBean(social, audit));
 
         switch (page) {
             case ACCOUNT:
@@ -170,4 +171,10 @@ public class FreeMarkerAccount implements Account {
         return this;
     }
 
+    @Override
+    public Account setFeatures(boolean social, boolean audit) {
+        this.social = social;
+        this.audit = audit;
+        return this;
+    }
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
new file mode 100644
index 0000000..6f8158b
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
@@ -0,0 +1,24 @@
+package org.keycloak.account.freemarker.model;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FeaturesBean {
+
+    private final boolean social;
+    private final boolean log;
+
+    public FeaturesBean(boolean social, boolean log) {
+        this.social = social;
+        this.log = log;
+    }
+
+    public boolean isSocial() {
+        return social;
+    }
+
+    public boolean isLog() {
+        return log;
+    }
+
+}
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 53dde7c..e626ddf 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
@@ -41,6 +41,10 @@ public class UrlBean {
         return Urls.accountTotpPage(baseURI, realm).toString();
     }
 
+    public String getLogUrl() {
+        return Urls.accountLogPage(baseURI, realm).toString();
+    }
+
     public String getTotpRemoveUrl() {
         return Urls.accountTotpRemove(baseURI, realm).toString();
     }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/log.ftl b/forms/common-themes/src/main/resources/theme/account/base/log.ftl
index 3f3c917..9752317 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/log.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/log.ftl
@@ -1,28 +1,32 @@
 <#import "template.ftl" as layout>
-<@layout.mainLayout active='social' bodyClass='social'; section>
+<@layout.mainLayout active='log' bodyClass='log'; section>
 
     <div class="row">
         <div class="col-md-10">
-            <h2>Social Accounts</h2>
+            <h2>Account Log</h2>
         </div>
     </div>
 
-    <table>
-        <th>
-            <td>${event.date}</td>
-            <td>${event.event}</td>
-            <td>${event.ipAddress}</td>
-            <td>${event.clientId}</td>
-        </th>
+    <table class="table">
+        <thead>
+        <tr>
+            <td>Date</td>
+            <td>Event</td>
+            <td>IP</td>
+            <td>Client</td>
+        </tr>
+        </thead>
 
+        <tbody>
         <#list log.events as event>
             <tr>
-                <td>${event.date}</td>
+                <td>${event.date?datetime}</td>
                 <td>${event.event}</td>
                 <td>${event.ipAddress}</td>
-                <td>${event.clientId}</td
+                <td>${event.client}</td
             </tr>
         </#list>
+        </tbody>
     </table>
 
 </@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 d6a2f5e..6a39817 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
@@ -42,7 +42,8 @@
                 <li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">Account</a></li>
                 <li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li>
                 <li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
-                <#if isSocialRealm?has_content><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
+                <#if features.social><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
+                <#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/Config.java b/model/api/src/main/java/org/keycloak/models/Config.java
index 6c53c61..7851ea8 100644
--- a/model/api/src/main/java/org/keycloak/models/Config.java
+++ b/model/api/src/main/java/org/keycloak/models/Config.java
@@ -30,7 +30,7 @@ public class Config {
     }
 
     public static String getAuditProvider() {
-        return System.getProperty(MODEL_PROVIDER_KEY);
+        return System.getProperty(MODEL_PROVIDER_KEY, "jpa");
     }
 
     public static void setAuditProvider(String provider) {
diff --git a/services/src/main/java/org/keycloak/services/DefaultProviderSession.java b/services/src/main/java/org/keycloak/services/DefaultProviderSession.java
index 748055b..f9e2948 100755
--- a/services/src/main/java/org/keycloak/services/DefaultProviderSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultProviderSession.java
@@ -11,24 +11,16 @@ import java.util.Set;
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class DefaultProviderSession implements ProviderSession {
-    private ProviderSessionFactory factory;
+    private DefaultProviderSessionFactory factory;
     private Map<Integer, Provider> providers = new HashMap<Integer, Provider>();
 
-    public DefaultProviderSession(ProviderSessionFactory factory) {
+    public DefaultProviderSession(DefaultProviderSessionFactory factory) {
         this.factory = factory;
     }
 
     public <T extends Provider> T getProvider(Class<T> clazz) {
-        Integer hash = clazz.hashCode();
-        T provider = (T) providers.get(hash);
-        if (provider == null) {
-            ProviderFactory<T> providerFactory = factory.getProviderFactory(clazz);
-            if (providerFactory != null) {
-                provider = providerFactory.create();
-                providers.put(hash, provider);
-            }
-        }
-        return provider;
+        String id = factory.getDefaultProvider(clazz);
+        return id != null ? getProvider(clazz, id) : null;
     }
 
     public <T extends Provider> T getProvider(Class<T> clazz, String id) {
diff --git a/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java
index c6b5943..6728559 100755
--- a/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java
@@ -41,6 +41,10 @@ public class DefaultProviderSessionFactory implements ProviderSessionFactory {
         return loader != null ? loader.providerIds() : null;
     }
 
+    public String getDefaultProvider(Class<? extends Provider> clazz) {
+        return defaultFactories.get(clazz);
+    }
+
     public void registerLoader(Class<? extends Provider> clazz, ProviderFactoryLoader loader) {
         loaders.put(clazz, loader);
 
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 8bc6269..9f47d7c 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -110,6 +110,7 @@ public class AccountService {
     private final SocialRequestManager socialRequestManager;
     private Account account;
     private Auth auth;
+    private AuditProvider auditProvider;
 
     public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager, Audit audit) {
         this.realm = realm;
@@ -120,7 +121,9 @@ public class AccountService {
     }
 
     public void init() {
-        account = AccountLoader.load().createAccount(uriInfo).setRealm(realm);
+        auditProvider = providers.getProvider(AuditProvider.class);
+
+        account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setFeatures(realm.isSocial(), auditProvider != null);
 
         auth = authManager.authenticate(realm, headers);
         if (auth != null) {
@@ -133,7 +136,6 @@ public class AccountService {
         return base;
     }
 
-
     private Response forwardToPage(String path, AccountPages page) {
         if (auth != null) {
             try {
@@ -195,9 +197,10 @@ public class AccountService {
     @Path("log")
     @GET
     public Response logPage() {
-        AuditProvider audit = providers.getProvider(AuditProvider.class);
-        List<Event> events = audit.createQuery().user(auth.getUser().getId()).maxResults(20).getResultList();
-        account.setEvents(events);
+        if (auth != null) {
+            List<Event> events = auditProvider.createQuery().user(auth.getUser().getId()).maxResults(20).getResultList();
+            account.setEvents(events);
+        }
         return forwardToPage("log", AccountPages.LOG);
     }
 
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 4147ada..55137c4 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
@@ -74,6 +74,10 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
     }
 
+    public static URI accountLogPage(URI baseUri, String realmId) {
+        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);
     }
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 115c618..0b3cd3d 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -44,6 +44,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-jpa</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-audit-jboss-logging</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 5ca96af..56a5ed4 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -32,6 +32,24 @@
         </properties>
     </persistence-unit>
 
+    <persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
+        <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+        <class>org.keycloak.audit.jpa.EventEntity</class>
+
+        <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+        <properties>
+            <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+            <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+            <property name="hibernate.connection.username" value="sa"/>
+            <property name="hibernate.connection.password" value=""/>
+            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+            <property name="hibernate.show_sql" value="false" />
+            <property name="hibernate.format_sql" value="true" />
+        </properties>
+    </persistence-unit>
+
     <!--
     <persistence-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
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 c2ad75e..981781f 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
@@ -28,6 +28,8 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.audit.Details;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.jpa.JpaAuditProviderFactory;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
@@ -38,6 +40,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountLogPage;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
 import org.keycloak.testsuite.pages.AccountTotpPage;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
@@ -45,12 +48,18 @@ import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -97,6 +106,9 @@ public class AccountTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected RegisterPage registerPage;
+
+    @WebResource
     protected AccountPasswordPage changePasswordPage;
 
     @WebResource
@@ -106,6 +118,9 @@ public class AccountTest {
     protected AccountTotpPage totpPage;
 
     @WebResource
+    protected AccountLogPage logPage;
+
+    @WebResource
     protected ErrorPage errorPage;
 
     private TimeBasedOTP totp = new TimeBasedOTP();
@@ -130,6 +145,8 @@ public class AccountTest {
                 appRealm.updateCredential(user, cred);
             }
         });
+
+        System.out.println(JpaAuditProviderFactory.class);
     }
 
     @Test
@@ -315,4 +332,43 @@ public class AccountTest {
         Assert.assertEquals("No access", errorPage.getError());
     }
 
+    @Test
+    public void viewLog() {
+        List<Event> e = new LinkedList<Event>();
+
+        loginPage.open();
+        loginPage.clickRegister();
+
+        registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password");
+
+        e.add(events.poll());
+        e.add(events.poll());
+
+        profilePage.open();
+        profilePage.updateProfile("view", "log2", "view-log@localhost");
+
+        e.add(events.poll());
+
+        logPage.open();
+
+        e.add(events.poll());
+
+        Collections.reverse(e);
+
+        Assert.assertTrue(logPage.isCurrent());
+
+        List<List<String>> actual = logPage.getEvents();
+
+        Assert.assertEquals(e.size(), actual.size());
+
+        Iterator<List<String>> itr = actual.iterator();
+        for (Event event : e) {
+            List<String> a = itr.next();
+            Assert.assertEquals(event.getEvent().replace('_', ' '), a.get(1));
+            Assert.assertEquals(event.getIpAddress(), a.get(2));
+            Assert.assertEquals(event.getClientId(), a.get(3));
+        }
+
+    }
+
 }
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
new file mode 100644
index 0000000..1f796d0
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
@@ -0,0 +1,60 @@
+/*
+ * 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.testsuite.Constants;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountLogPage extends AbstractAccountPage {
+
+    private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/log";
+
+    public boolean isCurrent() {
+        return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/log");
+    }
+
+    public void open() {
+        driver.navigate().to(PATH);
+    }
+
+    public List<List<String>> getEvents() {
+        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;
+    }
+
+}