keycloak-aplcache

KEYCLOAK-2879 UserResource

5/27/2016 10:53:25 AM

Details

diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index 2df2cbf..eebefe7 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -146,4 +146,8 @@ public interface UserResource {
     @Path("consents/{client}")
     public void revokeConsent(@PathParam("client") String clientId);
 
+    @POST
+    @Path("impersonation")
+    @Produces(MediaType.APPLICATION_JSON)
+    Map<String, Object> impersonate();
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ConsentPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ConsentPage.java
new file mode 100644
index 0000000..b8b244d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/ConsentPage.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ConsentPage extends AbstractPage {
+
+    @FindBy(id = "kc-login")
+    private WebElement submitButton;
+
+    public void confirm() {
+        submitButton.click();
+    }
+
+    @Override
+    public boolean isCurrent() {
+        return driver.getTitle().equalsIgnoreCase("grant access");
+    }
+
+    @Override
+    public void open() throws Exception {
+
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index fc7a341..68a8f8d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -294,6 +294,9 @@ public class ClientTest extends AbstractAdminTest {
         Map<String, Long> offlineSessionCount = realm.clients().get(id).getOfflineSessionCount();
         assertEquals(new Long(0), offlineSessionCount.get("count"));
 
+        List<UserSessionRepresentation> userSessions = realm.users().get(userId).getOfflineSessions(id);
+        assertEquals("There should be no offline sessions", 0, userSessions.size());
+
         oauth.realm(REALM_NAME);
         oauth.redirectUri(client.getRedirectUris().get(0));
         oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
@@ -307,6 +310,17 @@ public class ClientTest extends AbstractAdminTest {
         List<UserSessionRepresentation> offlineUserSessions = realm.clients().get(id).getOfflineUserSessions(0, 100);
         assertEquals(1, offlineUserSessions.size());
         assertEquals("testuser", offlineUserSessions.get(0).getUsername());
+
+        userSessions = realm.users().get(userId).getOfflineSessions(id);
+        assertEquals("There should be one offline session", 1, userSessions.size());
+        assertOfflineSession(offlineUserSessions.get(0), userSessions.get(0));
+    }
+
+    private void assertOfflineSession(UserSessionRepresentation expected, UserSessionRepresentation actual) {
+        assertEquals("id", expected.getId(), actual.getId());
+        assertEquals("userId", expected.getUserId(), actual.getUserId());
+        assertEquals("userName", expected.getUsername(), actual.getUsername());
+        assertEquals("clients", expected.getClients(), actual.getClients());
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java
new file mode 100644
index 0000000..fb0a863
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ConsentsTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.admin;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.pages.ConsentPage;
+import org.keycloak.testsuite.pages.LoginPage;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static org.keycloak.testsuite.admin.ApiUtil.createUserWithAdminClient;
+import static org.keycloak.testsuite.admin.ApiUtil.resetUserPassword;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ConsentsTest extends AbstractKeycloakTest {
+
+    final static String REALM_PROV_NAME = "provider";
+    final static String REALM_CONS_NAME = "consumer";
+
+    final static String IDP_OIDC_ALIAS = "kc-oidc-idp";
+    final static String IDP_OIDC_PROVIDER_ID = "keycloak-oidc";
+
+    final static String CLIENT_ID = "brokerapp";
+    final static String CLIENT_SECRET = "secret";
+
+    final static String USER_LOGIN = "testuser";
+    final static String USER_EMAIL = "user@localhost.com";
+    final static String USER_PASSWORD = "password";
+    final static String USER_FIRSTNAME = "User";
+    final static String USER_LASTNAME = "Tester";
+
+    protected RealmRepresentation createProviderRealm() {
+        RealmRepresentation realm = new RealmRepresentation();
+        realm.setRealm(REALM_PROV_NAME);
+        realm.setEnabled(true);
+
+        return realm;
+    }
+
+    protected RealmRepresentation createConsumerRealm() {
+        RealmRepresentation realm = new RealmRepresentation();
+        realm.setRealm(REALM_CONS_NAME);
+        realm.setEnabled(true);
+
+        return realm;
+    }
+
+    protected List<ClientRepresentation> createProviderClients() {
+        ClientRepresentation client = new ClientRepresentation();
+        client.setId(CLIENT_ID);
+        client.setName(CLIENT_ID);
+        client.setSecret(CLIENT_SECRET);
+        client.setEnabled(true);
+        client.setConsentRequired(true);
+
+        client.setRedirectUris(Collections.singletonList(getAuthRoot() +
+                "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint/*"));
+
+        client.setAdminUrl(getAuthRoot() +
+                "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_OIDC_ALIAS + "/endpoint");
+
+        return Collections.singletonList(client);
+    }
+
+    protected IdentityProviderRepresentation setUpIdentityProvider() {
+        IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
+
+        Map<String, String> config = idp.getConfig();
+
+        config.put("clientId", CLIENT_ID);
+        config.put("clientSecret", CLIENT_SECRET);
+        config.put("prompt", "login");
+        config.put("authorizationUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/auth");
+        config.put("tokenUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/token");
+        config.put("logoutUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/logout");
+        config.put("userInfoUrl", getAuthRoot() + "/auth/realms/" + REALM_PROV_NAME + "/protocol/openid-connect/userinfo");
+        config.put("defaultScope", "email profile");
+        config.put("backchannelSupported", "true");
+
+        return idp;
+    }
+
+    protected String getUserLogin() {
+        return USER_LOGIN;
+    }
+
+    protected String getUserPassword() {
+        return USER_PASSWORD;
+    }
+
+    protected String getUserEmail() {
+        return USER_EMAIL;
+    }
+
+    protected String getUserFirstName() {
+        return USER_FIRSTNAME;
+    }
+
+    protected String getUserLastName() {
+        return USER_LASTNAME;
+    }
+    protected String providerRealmName() {
+        return REALM_PROV_NAME;
+    }
+
+    protected String consumerRealmName() {
+        return REALM_CONS_NAME;
+    }
+
+    protected String getIDPAlias() {
+        return IDP_OIDC_ALIAS;
+    }
+
+
+    @Page
+    protected LoginPage accountLoginPage;
+
+    @Page
+    protected ConsentPage consentPage;
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation providerRealm = createProviderRealm();
+        RealmRepresentation consumerRealm = createConsumerRealm();
+
+        testRealms.add(providerRealm);
+        testRealms.add(consumerRealm);
+    }
+
+    @Before
+    public void createUser() {
+        log.debug("creating user for realm " + providerRealmName());
+
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername(getUserLogin());
+        user.setEmail(getUserEmail());
+        user.setFirstName(getUserFirstName());
+        user.setLastName(getUserLastName());
+        user.setEmailVerified(true);
+        user.setEnabled(true);
+
+        RealmResource realmResource = adminClient.realm(providerRealmName());
+        String userId = createUserWithAdminClient(realmResource, user);
+
+        resetUserPassword(realmResource.users().get(userId), getUserPassword(), false);
+    }
+
+    @Before
+    public void addIdentityProviderToProviderRealm() {
+        log.debug("adding identity provider to realm " + consumerRealmName());
+
+        RealmResource realm = adminClient.realm(consumerRealmName());
+        realm.identityProviders().create(setUpIdentityProvider());
+    }
+
+    @Before
+    public void addClients() {
+        List<ClientRepresentation> clients = createProviderClients();
+        if (clients != null) {
+            RealmResource providerRealm = adminClient.realm(providerRealmName());
+            for (ClientRepresentation client : clients) {
+                log.debug("adding client " + client.getName() + " to realm " + providerRealmName());
+
+                providerRealm.clients().create(client);
+            }
+        }
+    }
+
+    protected String getAuthRoot() {
+        return suiteContext.getAuthServerInfo().getContextRoot().toString();
+    }
+
+    protected IdentityProviderRepresentation createIdentityProvider(String alias, String providerId) {
+        IdentityProviderRepresentation identityProviderRepresentation = new IdentityProviderRepresentation();
+
+        identityProviderRepresentation.setAlias(alias);
+        identityProviderRepresentation.setProviderId(providerId);
+        identityProviderRepresentation.setEnabled(true);
+
+        return identityProviderRepresentation;
+    }
+
+    private void waitForPage(String title) {
+        long startAt = System.currentTimeMillis();
+
+        while (!driver.getTitle().toLowerCase().contains(title)
+                && System.currentTimeMillis() - startAt < 200) {
+            try {
+                Thread.sleep(5);
+            } catch (InterruptedException ignore) {}
+        }
+    }
+
+    @Test
+    public void testConsents() {
+        driver.navigate().to(getAccountUrl(consumerRealmName()));
+
+        log.debug("Clicking social " + getIDPAlias());
+        accountLoginPage.clickSocial(getIDPAlias());
+
+        if (!driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/")) {
+            log.debug("Not on provider realm page, url: " + driver.getCurrentUrl());
+        }
+
+        Assert.assertTrue("Driver should be on the provider realm page right now",
+                driver.getCurrentUrl().contains("/auth/realms/" + providerRealmName() + "/"));
+
+        log.debug("Logging in");
+        accountLoginPage.login(getUserLogin(), getUserPassword());
+
+        waitForPage("grant access");
+
+        Assert.assertTrue(consentPage.isCurrent());
+        consentPage.confirm();
+
+        Assert.assertTrue("We must be on correct realm right now",
+                driver.getCurrentUrl().contains("/auth/realms/" + consumerRealmName() + "/"));
+
+        UsersResource consumerUsers = adminClient.realm(consumerRealmName()).users();
+        Assert.assertTrue("There must be at least one user", consumerUsers.count() > 0);
+
+        List<UserRepresentation> users = consumerUsers.search("", 0, 5);
+
+        UserRepresentation foundUser = null;
+        for (UserRepresentation user : users) {
+            if (user.getUsername().equals(getUserLogin()) && user.getEmail().equals(getUserEmail())) {
+                foundUser = user;
+                break;
+            }
+        }
+
+        Assert.assertNotNull("There must be user " + getUserLogin() + " in realm " + consumerRealmName(),
+                foundUser);
+
+        // get user with the same username from provider realm
+        RealmResource providerRealm = adminClient.realm(providerRealmName());
+        users = providerRealm.users().search(null, foundUser.getFirstName(), foundUser.getLastName(), null, 0, 1);
+        Assert.assertEquals("Same user should be in provider realm", 1, users.size());
+
+        String userId = users.get(0).getId();
+        UserResource userResource = providerRealm.users().get(userId);
+
+        // list consents
+        List<Map<String, Object>> consents = userResource.getConsents();
+        Assert.assertEquals("There should be one consent", 1, consents.size());
+
+        Map<String, Object> consent = consents.get(0);
+        Assert.assertEquals("Consent should be given to " + CLIENT_ID, CLIENT_ID, consent.get("clientId"));
+
+        // list sessions
+        List<UserSessionRepresentation> sessions = userResource.getUserSessions();
+        Assert.assertEquals("There should be one active session", 1, sessions.size());
+
+        // revoke consent
+        userResource.revokeConsent(CLIENT_ID);
+
+        // list consents
+        consents = userResource.getConsents();
+        Assert.assertEquals("There should be no consents", 0, consents.size());
+
+        // list sessions
+        sessions = userResource.getUserSessions();
+        Assert.assertEquals("There should be no active session", 0, sessions.size());
+    }
+
+    private String getAccountUrl(String realmName) {
+        return getAuthRoot() + "/auth/realms/" + realmName + "/account";
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
index 7ec1da2..ca01045 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
@@ -21,6 +21,7 @@ import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.Config;
+import org.keycloak.admin.client.Keycloak;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.events.Details;
@@ -29,25 +30,20 @@ import org.keycloak.models.AdminRoles;
 import org.keycloak.models.Constants;
 import org.keycloak.models.ImpersonationConstants;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.services.resources.admin.AdminRoot;
 import org.keycloak.testsuite.AbstractKeycloakTest;
 import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
+import org.keycloak.testsuite.auth.page.AuthRealm;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.CredentialBuilder;
-import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.util.RealmBuilder;
 import org.keycloak.testsuite.util.UserBuilder;
 
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientRequestContext;
-import javax.ws.rs.client.ClientRequestFilter;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import java.io.IOException;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -85,21 +81,6 @@ public class ImpersonationTest extends AbstractKeycloakTest {
         testRealms.add(realm.build());
     }
 
-    private String createAdminToken(String username, String realm) {
-        try {
-            String password = username.equals("admin") ? "admin" : "password";
-            String clientId = realm.equals("master") ? Constants.ADMIN_CLI_CLIENT_ID : "myclient";
-            AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest(realm, username, password, null, clientId, null);
-            if (tokenResponse.getStatusCode() != 200) {
-                throw new RuntimeException("Failed to get token: " + tokenResponse.getErrorDescription());
-            }
-            events.clear();
-            return tokenResponse.getAccessToken();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
     @Test
     public void testImpersonateByMasterAdmin() {
         // test that composite is set up right for impersonation role
@@ -157,51 +138,65 @@ public class ImpersonationTest extends AbstractKeycloakTest {
     }
 
 
-
     protected void testSuccessfulImpersonation(String admin, String adminRealm) {
-        Client client = createClient(admin, adminRealm);
-        WebTarget impersonate = createImpersonateTarget(client);
-        Map data = impersonate.request().post(null, Map.class);
-        Assert.assertNotNull(data);
-        Assert.assertNotNull(data.get("redirect"));
-
-        // TODO Events not working
-        events.expect(EventType.IMPERSONATE)
-                .session(AssertEvents.isUUID())
-                .user(impersonatedUserId)
-                .detail(Details.IMPERSONATOR, admin)
-                .detail(Details.IMPERSONATOR_REALM, adminRealm)
-                .client((String) null).assertEvent();
-
-        client.close();
+
+        Keycloak client = login(admin, adminRealm);
+        try {
+            Map data = client.realms().realm("test").users().get(impersonatedUserId).impersonate();
+            Assert.assertNotNull(data);
+            Assert.assertNotNull(data.get("redirect"));
+
+            events.expect(EventType.IMPERSONATE)
+                    .session(AssertEvents.isUUID())
+                    .user(impersonatedUserId)
+                    .detail(Details.IMPERSONATOR, admin)
+                    .detail(Details.IMPERSONATOR_REALM, adminRealm)
+                    .client((String) null).assertEvent();
+        } finally {
+            client.close();
+        }
     }
 
     protected void testForbiddenImpersonation(String admin, String adminRealm) {
-        Client client = createClient(admin, adminRealm);
-        WebTarget impersonate = createImpersonateTarget(client);
-        Response response = impersonate.request().post(null);
-        response.close();
-        client.close();
+        Keycloak client = createAdminClient(adminRealm, establishClientId(adminRealm), admin);
+        try {
+            client.realms().realm("test").users().get(impersonatedUserId).impersonate();
+        } catch (ClientErrorException e) {
+            Assert.assertTrue(e.getMessage().indexOf("403 Forbidden") != -1);
+        } finally {
+            client.close();
+        }
     }
 
+    Keycloak createAdminClient(String realm, String clientId, String username) {
+        return createAdminClient(realm, clientId, username, null);
+    }
 
-    protected WebTarget createImpersonateTarget(Client client) {
-        UriBuilder authBase = UriBuilder.fromUri(getAuthServerRoot());
-        WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase));
-        WebTarget realmTarget = adminRealms.path("test");
-        return realmTarget.path("users").path(impersonatedUserId).path("impersonation");
+    String establishClientId(String realm) {
+        return realm.equals("master") ? Constants.ADMIN_CLI_CLIENT_ID : "myclient";
     }
 
-    protected Client createClient(String admin, String adminRealm) {
-        String token = createAdminToken(admin, adminRealm);
-        final String authHeader = "Bearer " + token;
-        ClientRequestFilter authFilter = new ClientRequestFilter() {
-            @Override
-            public void filter(ClientRequestContext requestContext) throws IOException {
-                requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
-            }
-        };
-        return javax.ws.rs.client.ClientBuilder.newBuilder().register(authFilter).build();
+    Keycloak createAdminClient(String realm, String clientId, String username, String password) {
+        if (password == null) {
+            password = username.equals("admin") ? "admin" : "password";
+        }
+        return Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
+                realm, username, password, clientId);
     }
 
+    private Keycloak login(String username, String realm) {
+        String clientId = establishClientId(realm);
+        Keycloak client = createAdminClient(realm, clientId, username);
+
+        client.tokenManager().grantToken();
+        // only poll for LOGIN event if realm is not master
+        // - since for master testing event listener is not installed
+        if (!AuthRealm.MASTER.equals(realm)) {
+            EventRepresentation e = events.poll();
+            Assert.assertEquals("Event type", EventType.LOGIN.toString(), e.getType());
+            Assert.assertEquals("Client ID", clientId, e.getClientId());
+            Assert.assertEquals("Username", username, e.getDetails().get("username"));
+        }
+        return client;
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java
new file mode 100644
index 0000000..4831e29
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTotpTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.admin;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.representations.idm.AdminEventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.resources.AccountService;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.TestRealmKeycloakTest;
+import org.keycloak.testsuite.pages.AccountTotpPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.LoginPage;
+
+import javax.ws.rs.core.UriBuilder;
+import java.util.List;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class UserTotpTest extends TestRealmKeycloakTest {
+
+    private static final UriBuilder BASE = UriBuilder.fromUri("http://localhost:8180/auth");
+    public static String ACCOUNT_REDIRECT = AccountService.loginRedirectUrl(BASE.clone()).build("test").toString();
+
+    @Rule
+    public AssertEvents events = new AssertEvents(this);
+
+    @Page
+    protected AccountTotpPage totpPage;
+
+    @Page
+    protected AccountUpdateProfilePage profilePage;
+
+    @Page
+    protected LoginPage loginPage;
+
+    private TimeBasedOTP totp = new TimeBasedOTP();
+
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Test
+    public void setupTotp() {
+        totpPage.open();
+        loginPage.login("test-user@localhost", "password");
+
+        events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=totp").assertEvent();
+
+        Assert.assertTrue(totpPage.isCurrent());
+
+        Assert.assertFalse(driver.getPageSource().contains("Remove Google"));
+
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
+
+        Assert.assertEquals("Mobile authenticator configured.", profilePage.getSuccess());
+
+        events.expectAccount(EventType.UPDATE_TOTP).assertEvent();
+
+        Assert.assertTrue(driver.getPageSource().contains("pficon-delete"));
+
+        List<UserRepresentation> users = adminClient.realms().realm("test").users().search("test-user@localhost", null, null, null, 0, 1);
+        String userId = users.get(0).getId();
+
+        adminClient.realms().realm("test").users().get(userId).removeTotp();
+
+        totpPage.open();
+        Assert.assertFalse(driver.getPageSource().contains("pficon-delete"));
+
+        AdminEventRepresentation event = testingClient.testing().pollAdminEvent();
+        Assert.assertNotNull(event);
+        Assert.assertEquals(OperationType.ACTION.name(), event.getOperationType());
+        Assert.assertEquals("users/" + userId + "/remove-totp", event.getResourcePath());
+    }
+}