keycloak-aplcache

Merge pull request #2678 from stianst/KEYCLOAK-2850-1.9 KEYCLOAK-2850

4/20/2016 9:48:07 AM

Details

diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
index 84bd64c..3fd7712 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java
@@ -129,7 +129,7 @@ public abstract class AbstractKeycloakTest {
     private boolean resetTimeOffset;
 
     @Before
-    public void beforeAbstractKeycloakTest() {
+    public void beforeAbstractKeycloakTest() throws Exception {
         adminClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth",
                 MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
         deleteMeOAuthClient = new DeleteMeOAuthClient(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
index 0f8ee1f..a60926e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java
@@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin;
 import org.jboss.logging.Logger;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.RoleResource;
 import org.keycloak.admin.client.resource.UserResource;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
@@ -85,6 +86,10 @@ public class ApiUtil {
         return null;
     }
 
+    public static RoleResource findClientRoleByName(ClientResource client, String role) {
+        return client.roles().get(role);
+    }
+
     public static UserRepresentation findUserByUsername(RealmResource realm, String username) {
         UserRepresentation user = null;
         List<UserRepresentation> ur = realm.users().search(username, null, null);
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
new file mode 100755
index 0000000..afe11a3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.junit.Assert;
+import org.junit.Test;
+import org.keycloak.Config;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+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.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.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.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests Undertow Adapter
+ *
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class ImpersonationTest extends AbstractKeycloakTest {
+
+    private AssertEvents events;
+
+    private String impersonatedUserId;
+
+    @Override
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+        events = new AssertEvents(this);
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmBuilder realm = RealmBuilder.create().name("test").testEventListener();
+
+        realm.client(ClientBuilder.create().clientId("myclient").publicClient().directAccessGrants());
+
+        impersonatedUserId = KeycloakModelUtils.generateId();
+
+        realm.user(UserBuilder.create().id(impersonatedUserId).username("test-user@localhost"));
+        realm.user(UserBuilder.create().username("realm-admin").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN));
+        realm.user(UserBuilder.create().username("impersonator").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, ImpersonationConstants.IMPERSONATION_ROLE).role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_USERS));
+        realm.user(UserBuilder.create().username("bad-impersonator").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.MANAGE_USERS));
+
+        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 = oauthClient.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
+        testSuccessfulImpersonation("admin", Config.getAdminRealm());
+    }
+
+    @Test
+    public void testImpersonateByMasterImpersonator() {
+        Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-impersonator").build());
+        String userId = ApiUtil.getCreatedId(response);
+        response.close();
+
+        UserResource user = adminClient.realm("master").users().get(userId);
+        user.resetPassword(CredentialBuilder.create().password("password").build());
+
+        ClientResource testRealmClient = ApiUtil.findClientResourceByClientId(adminClient.realm("master"), "test-realm");
+
+        List<RoleRepresentation> roles = new LinkedList<>();
+        roles.add(ApiUtil.findClientRoleByName(testRealmClient, AdminRoles.VIEW_USERS).toRepresentation());
+        roles.add(ApiUtil.findClientRoleByName(testRealmClient, ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation());
+
+        user.roles().clientLevel(testRealmClient.toRepresentation().getId()).add(roles);
+
+        testSuccessfulImpersonation("master-impersonator", Config.getAdminRealm());
+
+        adminClient.realm("master").users().get(userId).remove();
+    }
+
+    @Test
+    public void testImpersonateByTestImpersonator() {
+        testSuccessfulImpersonation("impersonator", "test");
+    }
+
+    @Test
+    public void testImpersonateByTestAdmin() {
+        // test that composite is set up right for impersonation role
+        testSuccessfulImpersonation("realm-admin", "test");
+    }
+
+    @Test
+    public void testImpersonateByTestBadImpersonator() {
+        testForbiddenImpersonation("bad-impersonator", "test");
+    }
+
+    @Test
+    public void testImpersonateByMastertBadImpersonator() {
+        Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-bad-impersonator").build());
+        String userId = ApiUtil.getCreatedId(response);
+        response.close();
+        adminClient.realm("master").users().get(userId).resetPassword(CredentialBuilder.create().password("password").build());
+
+        testForbiddenImpersonation("master-bad-impersonator", Config.getAdminRealm());
+
+        adminClient.realm("master").users().get(userId).remove();
+    }
+
+
+
+    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();
+    }
+
+    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();
+    }
+
+
+    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");
+    }
+
+    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();
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 6df6511..6a4a554 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -64,7 +64,6 @@ public class AssertEvents {
     private RealmRepresentation realmRep;
     private AbstractKeycloakTest context;
     private PublicKey realmPublicKey;
-    private UserRepresentation defaultUser;
 
     public AssertEvents(AbstractKeycloakTest ctx) throws Exception {
         context = ctx;
@@ -74,11 +73,6 @@ public class AssertEvents {
         String pubKeyString = realmRep.getPublicKey();
         realmPublicKey = PemUtils.decodePublicKey(pubKeyString);
 
-        defaultUser = getUser(DEFAULT_USERNAME);
-        if (defaultUser == null) {
-            throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm.");
-        }
-
         defaultEventsQueueUri = getAuthServerEventsQueueUri();
     }
 
@@ -192,7 +186,7 @@ public class AssertEvents {
         return new ExpectedEvent()
                 .realm(realmRep.getId())
                 .client(DEFAULT_CLIENT_ID)
-                .user(defaultUser.getId())
+                .user(defaultUserId())
                 .ipAddress(DEFAULT_IP_ADDRESS)
                 .session((String) null)
                 .event(event);
@@ -357,6 +351,34 @@ public class AssertEvents {
         };
     }
 
+    public Matcher<String> defaultUserId() {
+        return new TypeSafeMatcher<String>() {
+            private String userId;
+
+            @Override
+            protected boolean matchesSafely(String item) {
+                return item.equals(getUserId());
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(getUserId());
+            }
+
+            private String getUserId() {
+                if (userId == null) {
+                    UserRepresentation user = getUser(DEFAULT_USERNAME);
+                    if (user == null) {
+                        throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm.");
+                    }
+                    userId = user.getId();
+                }
+                return userId;
+            }
+
+        };
+    }
+
     private EventRepresentation fetchNextEvent() {
         CloseableHttpClient httpclient = HttpClients.createDefault();
         try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java
new file mode 100644
index 0000000..a0b3bf7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java
@@ -0,0 +1,66 @@
+/*
+ * 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.util;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientBuilder {
+
+    private ClientRepresentation rep = new ClientRepresentation();
+
+    public static ClientBuilder create() {
+        return new ClientBuilder();
+    }
+
+    private ClientBuilder() {
+        rep.setEnabled(true);
+    }
+
+    public ClientBuilder id(String id) {
+        rep.setId(id);
+        return this;
+    }
+
+    public ClientBuilder clientId(String clientId) {
+        rep.setClientId(clientId);
+        return this;
+    }
+
+    public ClientBuilder publicClient() {
+        rep.setPublicClient(true);
+        return this;
+    }
+
+    public ClientBuilder directAccessGrants() {
+        rep.setDirectAccessGrantsEnabled(true);
+        return this;
+    }
+
+    public ClientRepresentation build() {
+        return rep;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java
new file mode 100644
index 0000000..038f4fb
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * 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.util;
+
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class CredentialBuilder {
+
+    private CredentialRepresentation rep = new CredentialRepresentation();
+
+    public static CredentialBuilder create() {
+        return new CredentialBuilder();
+    }
+
+    private CredentialBuilder() {
+    }
+
+    public CredentialBuilder password(String password) {
+        rep.setType(CredentialRepresentation.PASSWORD);
+        rep.setValue(password);
+        return this;
+    }
+
+    public CredentialRepresentation build() {
+        return rep;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
new file mode 100644
index 0000000..8859f03
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.util;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import java.util.LinkedList;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmBuilder {
+
+    private RealmRepresentation rep = new RealmRepresentation();
+
+    public static RealmBuilder create() {
+        return new RealmBuilder();
+    }
+
+    private RealmBuilder() {
+        rep.setEnabled(true);
+    }
+
+    public RealmBuilder name(String name) {
+        rep.setRealm(name);
+        return this;
+    }
+
+    public RealmBuilder testEventListener() {
+        if (rep.getEventsListeners() == null) {
+            rep.setEventsListeners(new LinkedList<String>());
+        }
+
+        rep.getEventsListeners().add("event-queue");
+        return this;
+    }
+
+    public RealmBuilder client(ClientBuilder client) {
+        return client(client.build());
+    }
+
+    public RealmBuilder client(ClientRepresentation client) {
+        if (rep.getClients() == null) {
+            rep.setClients(new LinkedList<ClientRepresentation>());
+        }
+        rep.getClients().add(client);
+        return this;
+    }
+
+    public RealmBuilder user(UserBuilder user) {
+        return user(user.build());
+    }
+
+    public RealmBuilder user(UserRepresentation user) {
+        if (rep.getUsers() == null) {
+            rep.setUsers(new LinkedList<UserRepresentation>());
+        }
+        rep.getUsers().add(user);
+        return this;
+    }
+
+    public RealmRepresentation build() {
+        return rep;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java
new file mode 100644
index 0000000..d46d160
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+import org.keycloak.representations.idm.RoleRepresentation;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RoleBuilder {
+
+    private RoleRepresentation rep = new RoleRepresentation();
+
+    public static RoleBuilder create() {
+        return new RoleBuilder();
+    }
+
+    private RoleBuilder() {
+    }
+
+    public RoleBuilder id(String id) {
+        rep.setId(id);
+        return this;
+    }
+
+    public RoleBuilder name(String name) {
+        rep.setName(name);
+        return this;
+    }
+
+    public RoleRepresentation build() {
+        return rep;
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java
new file mode 100644
index 0000000..e976a45
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java
@@ -0,0 +1,87 @@
+/*
+ * 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.util;
+
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserBuilder {
+
+    private UserRepresentation rep = new UserRepresentation();
+
+    public static UserBuilder create() {
+        return new UserBuilder();
+    }
+
+    private UserBuilder() {
+        rep.setEnabled(true);
+    }
+
+    public UserBuilder id(String id) {
+        rep.setId(id);
+        return this;
+    }
+
+    public UserBuilder username(String username) {
+        rep.setUsername(username);
+        return this;
+    }
+
+    public UserBuilder password(String password) {
+        if (rep.getCredentials() == null) {
+            rep.setCredentials(new LinkedList<CredentialRepresentation>());
+        }
+
+        CredentialRepresentation credential = new CredentialRepresentation();
+        credential.setType(CredentialRepresentation.PASSWORD);
+        credential.setValue(password);
+
+        rep.getCredentials().add(credential);
+        return this;
+    }
+
+    public UserBuilder role(String role) {
+        if (rep.getRealmRoles() == null) {
+            rep.setRealmRoles(new LinkedList<String>());
+        }
+        return this;
+    }
+
+    public UserBuilder role(String client, String role) {
+        if (rep.getClientRoles() == null) {
+            rep.setClientRoles(new HashMap<String, List<String>>());
+        }
+        if (rep.getClientRoles().get(client) == null) {
+            rep.getClientRoles().put(client, new LinkedList<String>());
+        }
+        rep.getClientRoles().get(client).add(role);
+        return this;
+    }
+
+    public UserRepresentation build() {
+        return rep;
+    }
+
+}