keycloak-aplcache

Admin REST client

7/14/2014 9:51:48 AM

Changes

Details

diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml
new file mode 100644
index 0000000..ca53e39
--- /dev/null
+++ b/integration/admin-client/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-4-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-admin-client</artifactId>
+    <name>Keycloak Admin REST Client</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${keycloak.apache.httpcomponents.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>jaxrs-api</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jaxrs</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-client</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.resteasy</groupId>
+            <artifactId>resteasy-jackson-provider</artifactId>
+            <version>${resteasy.version.latest}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java
new file mode 100644
index 0000000..47285d6
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java
@@ -0,0 +1,76 @@
+package org.keycloak.admin.client;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class Config {
+
+    private String serverUrl;
+    private String realm;
+    private String username;
+    private String password;
+    private String clientId;
+    private String clientSecret;
+
+    public Config(String serverUrl, String realm, String username, String password, String clientId, String clientSecret) {
+        this.serverUrl = serverUrl;
+        this.realm = realm;
+        this.username = username;
+        this.password = password;
+        this.clientId = clientId;
+        this.clientSecret = clientSecret;
+    }
+
+    public String getServerUrl() {
+        return serverUrl;
+    }
+
+    public void setServerUrl(String serverUrl) {
+        this.serverUrl = serverUrl;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public void setClientId(String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getClientSecret() {
+        return clientSecret;
+    }
+
+    public void setClientSecret(String clientSecret) {
+        this.clientSecret = clientSecret;
+    }
+
+    public boolean isPublicClient(){
+        return clientSecret == null;
+    }
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java
new file mode 100644
index 0000000..7f10374
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java
@@ -0,0 +1,50 @@
+package org.keycloak.admin.client;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
+import org.keycloak.admin.client.resource.BearerAuthFilter;
+import org.keycloak.admin.client.resource.KeycloakAdminFactory;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.admin.client.resource.RealmsResource;
+import org.keycloak.admin.client.token.TokenManager;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class Keycloak {
+
+    private final Config config;
+    private final TokenManager tokenManager;
+
+    private Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){
+        config = new Config(serverUrl, realm, username, password, clientId, clientSecret);
+        tokenManager = new TokenManager(config);
+    }
+
+    public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){
+        return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret);
+    }
+
+    public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId){
+        return new Keycloak(serverUrl, realm, username, password, clientId, null);
+    }
+
+    public RealmsResource realms(){
+        ResteasyClient client = new ResteasyClientBuilder().build();
+        ResteasyWebTarget target = client.target(config.getServerUrl());
+
+        target.register(new BearerAuthFilter(tokenManager.getAccessTokenString()));
+
+        return target.proxy(RealmsResource.class);
+    }
+
+    public RealmResource realm(String realmName){
+        return realms().realm(realmName);
+    }
+
+    public TokenManager tokenManager(){
+        return tokenManager;
+    }
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationResource.java
new file mode 100644
index 0000000..cca4442
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationResource.java
@@ -0,0 +1,90 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.representations.idm.ClaimRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.Set;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface ApplicationResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public ApplicationRepresentation toRepresentation();
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void update(ApplicationRepresentation applicationRepresentation);
+
+    @DELETE
+    public void remove();
+
+    @GET
+    @Path("allowed-origins")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Set<String> getAllowedOrigins();
+
+    @PUT
+    @Path("allowed-origins")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void updateAllowedOrigins(Set<String> allowedOrigins);
+
+    @DELETE
+    @Path("allowed-origins")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void removeAllowedOrigins(Set<String> originsToRemove);
+
+    @GET
+    @Path("claims")
+    @Produces(MediaType.APPLICATION_JSON)
+    public ClaimRepresentation getClaims();
+
+    @PUT
+    @Path("claims")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void updateClaims(ClaimRepresentation claimRepresentation);
+
+    @POST
+    @Path("client-secret")
+    @Produces(MediaType.APPLICATION_JSON)
+    public CredentialRepresentation generateNewSecret();
+
+    @GET
+    @Path("client-secret")
+    @Produces(MediaType.APPLICATION_JSON)
+    public CredentialRepresentation getSecret();
+
+    @GET
+    @Path("installation/jboss")
+    @Produces(MediaType.APPLICATION_XML)
+    public String getInstallationJbossXml();
+
+    @GET
+    @Path("installation/json")
+    @Produces(MediaType.APPLICATION_JSON)
+    public String getInstallationJson();
+
+    @POST
+    @Path("logout-all")
+    public void logoutAllUsers();
+
+    @POST
+    @Path("logout-user/{username}")
+    public void logoutUser(@PathParam("username") String username);
+
+    @POST
+    @Path("push-revocation")
+    public void pushRevocation();
+
+    @Path("/scope-mappings")
+    public RoleMappingResource getScopeMappings();
+
+    @Path("/roles")
+    public RolesResource roles();
+
+}
\ No newline at end of file
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationsResource.java
new file mode 100644
index 0000000..8fd4800
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ApplicationsResource.java
@@ -0,0 +1,27 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ApplicationRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface ApplicationsResource {
+
+    @Path("{appName}")
+    public ApplicationResource get(@PathParam("appName") String appName);
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void create(ApplicationRepresentation applicationRepresentation);
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<ApplicationRepresentation> findAll();
+
+
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java
new file mode 100644
index 0000000..323b1a7
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BasicAuthFilter.java
@@ -0,0 +1,31 @@
+package org.keycloak.admin.client.resource;
+
+import org.jboss.resteasy.util.Base64;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.HttpHeaders;
+import java.io.IOException;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class BasicAuthFilter implements ClientRequestFilter {
+
+    private final String username;
+    private final String password;
+
+    public BasicAuthFilter(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException {
+        String pair = username + ":" + password;
+        String authHeader = "Basic " + new String(Base64.encodeBytes(pair.getBytes()));
+        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+    }
+    
+    
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BearerAuthFilter.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BearerAuthFilter.java
new file mode 100644
index 0000000..1442513
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/BearerAuthFilter.java
@@ -0,0 +1,25 @@
+package org.keycloak.admin.client.resource;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.HttpHeaders;
+import java.io.IOException;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class BearerAuthFilter implements ClientRequestFilter {
+
+    private final String tokenString;
+
+    public BearerAuthFilter(String tokenString) {
+        this.tokenString = tokenString;
+    }
+
+    @Override
+    public void filter(ClientRequestContext requestContext) throws IOException {
+        String authHeader = "Bearer " + tokenString;
+        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader);
+    }
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeycloakAdminFactory.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeycloakAdminFactory.java
new file mode 100644
index 0000000..3b3d11f
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/KeycloakAdminFactory.java
@@ -0,0 +1,27 @@
+package org.keycloak.admin.client.resource;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
+import org.keycloak.admin.client.Config;
+import org.keycloak.admin.client.token.TokenManager;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class KeycloakAdminFactory {
+
+    private KeycloakAdminFactory(){}
+
+    public static RealmResource getRealm(Config config, TokenManager tokenManager, String realmName){
+        ResteasyClient client = new ResteasyClientBuilder().build();
+        ResteasyWebTarget target = client.target(config.getServerUrl());
+
+        target.register(new BearerAuthFilter(tokenManager.getAccessTokenString()));
+
+        RealmsResource adminRoot = target.proxy(RealmsResource.class);
+
+        return adminRoot.realm(realmName);
+    }
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientResource.java
new file mode 100644
index 0000000..ceeddb2
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientResource.java
@@ -0,0 +1,49 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.ClaimRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.OAuthClientRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface OAuthClientResource {
+
+    @GET
+    public OAuthClientRepresentation toRepresentation();
+
+    @PUT
+    public void update(OAuthClientRepresentation oAuthClientRepresentation);
+
+    @DELETE
+    public void remove();
+
+    @GET
+    @Path("claims")
+    public ClaimRepresentation getClaims();
+
+    @PUT
+    @Path("claims")
+    public ClaimRepresentation updateClaims(ClaimRepresentation claimRepresentation);
+
+    @POST
+    @Path("client-secret")
+    public CredentialRepresentation generateNewSecret();
+
+    @GET
+    @Path("client-secret")
+    public CredentialRepresentation getSecret();
+
+    @GET
+    @Path("installation")
+    public String getInstallationJson();
+
+    @Path("/scope-mappings")
+    public RoleMappingResource getScopeMappings();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientsResource.java
new file mode 100644
index 0000000..68e3f81
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/OAuthClientsResource.java
@@ -0,0 +1,25 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.OAuthClientRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface OAuthClientsResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<OAuthClientRepresentation> findAll();
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void create(OAuthClientRepresentation oAuthClientRepresentation);
+
+    @Path("{oAuthClientId}")
+    public OAuthClientResource get(@PathParam("oAuthClientId") String oAuthClientId);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
new file mode 100644
index 0000000..0ecb18b
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -0,0 +1,35 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.RealmRepresentation;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface RealmResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public RealmRepresentation toRepresentation();
+
+    @Path("applications")
+    public ApplicationsResource applications();
+
+    @Path("users")
+    public UsersResource users();
+
+    @Path("oauth-clients")
+    public OAuthClientsResource oAuthClients();
+
+    @Path("roles")
+    public RolesResource roles();
+
+    @DELETE
+    public void remove();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmsResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmsResource.java
new file mode 100644
index 0000000..9d8275c
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmsResource.java
@@ -0,0 +1,32 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.RealmRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Path("/admin/realms")
+@Consumes(MediaType.APPLICATION_JSON)
+public interface RealmsResource {
+
+    @Path("/{realm}")
+    public RealmResource realm(@PathParam("realm") String realm);
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void create(RealmRepresentation realmRepresentation);
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<RealmRepresentation> findAll();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java
new file mode 100644
index 0000000..7d01d9f
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleMappingResource.java
@@ -0,0 +1,24 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.MappingsRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface RoleMappingResource {
+
+    @GET
+    public MappingsRepresentation getAll();
+
+    @Path("realm")
+    public RoleScopeResource realmLevel();
+
+    @Path("applications/{appName}")
+    public RoleScopeResource applicationLevel(@PathParam("appName") String appName);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java
new file mode 100644
index 0000000..84a292f
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java
@@ -0,0 +1,51 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.RoleRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface RoleResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public RoleRepresentation toRepresentation();
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void update(RoleRepresentation roleRepresentation);
+
+    @DELETE
+    public void remove();
+
+    @GET
+    @Path("composites")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Set<RoleRepresentation> getChildren();
+
+    @GET
+    @Path("composites/realm")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Set<RoleRepresentation> getRealmLevelChildren();
+
+    @GET
+    @Path("composites/application/{appName}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Set<RoleRepresentation> getApplicationLevelChildren(@PathParam("appName") String appName);
+
+    @POST
+    @Path("composites")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void addChildren(List<RoleRepresentation> rolesToAdd);
+
+    @DELETE
+    @Path("composites")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void removeChildren(List<RoleRepresentation> rolesToRemove);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleScopeResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleScopeResource.java
new file mode 100644
index 0000000..21a4645
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleScopeResource.java
@@ -0,0 +1,33 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.RoleRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface RoleScopeResource {
+
+    @GET
+    public List<RoleRepresentation> listAll();
+
+    @GET
+    @Path("available")
+    public List<RoleRepresentation> listAvailable();
+
+    @GET
+    @Path("composite")
+    public List<RoleRepresentation> listEffective();
+
+    @POST
+    public void add(List<RoleRepresentation> rolesToAdd);
+
+    @DELETE
+    public void remove(List<RoleRepresentation> rolesToRemove);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolesResource.java
new file mode 100644
index 0000000..6d705f5
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RolesResource.java
@@ -0,0 +1,26 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.RoleRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public interface RolesResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<RoleRepresentation> list();
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void create(RoleRepresentation roleRepresentation);
+
+    @Path("{roleName}")
+    public RoleResource get(@PathParam("roleName") String roleName);
+
+
+}
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
new file mode 100644
index 0000000..ce5dde2
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -0,0 +1,61 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.adapters.action.UserStats;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.SocialLinkRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.UserSessionRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public interface UserResource {
+
+    @GET
+    public UserRepresentation toRepresentation();
+
+    @PUT
+    public void update(UserRepresentation userRepresentation);
+
+    @DELETE
+    public void remove();
+
+    @POST
+    @Path("logout")
+    public void logout();
+
+    @PUT
+    @Path("remove-totp")
+    public void removeTotp();
+
+    @PUT
+    @Path("reset-password")
+    public void resetPassword(CredentialRepresentation credentialRepresentation);
+
+    @PUT
+    @Path("reset-password-email")
+    public void resetPasswordEmail();
+
+    @GET
+    @Path("session-stats")
+    public Map<String, UserStats> getUserStats();
+
+    @GET
+    @Path("sessions")
+    public List<UserSessionRepresentation> getUserSessions();
+
+    @GET
+    @Path("social-links")
+    public List<SocialLinkRepresentation> getSocialLinks();
+
+    @Path("role-mappings")
+    public RoleMappingResource roles();
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java
new file mode 100644
index 0000000..640a900
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java
@@ -0,0 +1,29 @@
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+public interface UsersResource {
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserRepresentation> search(@QueryParam("username")  String username,
+                                           @QueryParam("firstName") String firstName,
+                                           @QueryParam("lastName")  String lastName,
+                                           @QueryParam("email")     String email);
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public List<UserRepresentation> search(@QueryParam("search") String search);
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void create(UserRepresentation userRepresentation);
+
+    @Path("{username}")
+    public UserResource get(@PathParam("username") String username);
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
new file mode 100644
index 0000000..3e3d923
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
@@ -0,0 +1,99 @@
+package org.keycloak.admin.client.token;
+
+import org.jboss.resteasy.client.jaxrs.ResteasyClient;
+import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
+import org.keycloak.admin.client.Config;
+import org.keycloak.admin.client.resource.BasicAuthFilter;
+import org.keycloak.representations.AccessTokenResponse;
+
+import javax.ws.rs.core.Form;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+public class TokenManager {
+
+    private AccessTokenResponse currentToken;
+    private Date expirationTime;
+    private Config config;
+
+    public TokenManager(Config config){
+        this.config = config;
+    }
+
+    public String getAccessTokenString(){
+        return getAccessToken().getToken();
+    }
+
+    public AccessTokenResponse getAccessToken(){
+        if(currentToken == null){
+            grantToken();
+        }else if(tokenExpired()){
+            refreshToken();
+        }
+        return currentToken;
+    }
+
+    public AccessTokenResponse grantToken(){
+        ResteasyClient client = new ResteasyClientBuilder().build();
+        ResteasyWebTarget target = client.target(config.getServerUrl());
+
+        Form form = new Form()
+                .param("username", config.getUsername())
+                .param("password", config.getPassword());
+
+        if(config.isPublicClient()){
+            form.param("client_id", config.getClientId());
+        } else {
+            target.register(new BasicAuthFilter(config.getClientId(), config.getClientSecret()));
+        }
+
+        TokenService tokenService = target.proxy(TokenService.class);
+
+        AccessTokenResponse response = tokenService.grantToken(config.getRealm(), form.asMap());
+
+        defineCurrentToken(response);
+        return response;
+    }
+
+    public AccessTokenResponse refreshToken(){
+        ResteasyClient client = new ResteasyClientBuilder().build();
+        ResteasyWebTarget target = client.target(config.getServerUrl());
+
+        Form form = new Form()
+                .param("username", config.getUsername())
+                .param("password", config.getPassword());
+
+        if(config.isPublicClient()){
+            form.param("client_id", config.getClientId());
+        } else {
+            target.register(new BasicAuthFilter(config.getClientId(), config.getClientSecret()));
+        }
+
+        TokenService tokenService = target.proxy(TokenService.class);
+
+        AccessTokenResponse response = tokenService.refreshToken(config.getRealm(), form.asMap());
+
+        defineCurrentToken(response);
+        return response;
+    }
+
+    private void setExpirationTime() {
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.SECOND, (int) currentToken.getExpiresIn());
+        expirationTime = cal.getTime();
+    }
+
+    private boolean tokenExpired() {
+        return new Date().after(expirationTime);
+    }
+
+    private void defineCurrentToken(AccessTokenResponse accessTokenResponse){
+        currentToken = accessTokenResponse;
+        setExpirationTime();
+    }
+
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
new file mode 100644
index 0000000..ff4c8f7
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenService.java
@@ -0,0 +1,24 @@
+package org.keycloak.admin.client.token;
+
+import org.keycloak.representations.AccessTokenResponse;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * @author rodrigo.sasaki@icarros.com.br
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+public interface TokenService {
+
+    @POST
+    @Path("/realms/{realm}/tokens/grants/access")
+    public AccessTokenResponse grantToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
+
+    @POST
+    @Path("/realms/{realm}/tokens/refresh")
+    public AccessTokenResponse refreshToken(@PathParam("realm") String realm, MultivaluedMap<String, String> map);
+
+}
diff --git a/integration/pom.xml b/integration/pom.xml
index 6efed51..02ce18f 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -26,5 +26,6 @@
         <module>as7-eap-subsystem</module>
         <module>js</module>
         <module>installed</module>
+        <module>admin-client</module>
     </modules>
 </project>
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 55fe1a0..3aab521 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -31,6 +31,11 @@
             <type>pom</type>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-admin-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java
new file mode 100644
index 0000000..4df9370
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java
@@ -0,0 +1,110 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.models.Constants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.representations.idm.OAuthClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.rule.KeycloakRule;
+
+import javax.ws.rs.ClientErrorException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractClientTest {
+
+    protected static final String REALM_NAME = "admin-client-test";
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule();
+
+    protected Keycloak keycloak;
+    protected RealmResource realm;
+
+    @Before
+    public void before() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                adminstrationRealm.setPasswordCredentialGrantAllowed(true);
+
+                RealmModel testRealm = manager.createRealm(REALM_NAME);
+                testRealm.setEnabled(true);
+            }
+        });
+
+        keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_APPLICATION);
+        realm = keycloak.realm(REALM_NAME);
+    }
+
+    @After
+    public void after() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                adminstrationRealm.setPasswordCredentialGrantAllowed(false);
+
+                RealmModel realm = manager.getRealmByName(REALM_NAME);
+                if (realm != null) {
+                    manager.removeRealm(realm);
+                }
+            }
+        });
+    }
+
+    public static <T> void assertNames(List<T> actual, String... expected) {
+        Arrays.sort(expected);
+        String[] actualNames = names(actual);
+        assertArrayEquals("Expected: " + Arrays.toString(expected) + ", was: " + Arrays.toString(actualNames), expected, actualNames);
+    }
+
+    public static <T> List<T> sort(List<T> list) {
+        Collections.sort(list, new Comparator<Object>() {
+            @Override
+            public int compare(Object o1, Object o2) {
+                return name(o1).compareTo(name(o2));
+            }
+        });
+        return list;
+    }
+
+    public static <T> String[] names(List<T> list) {
+        String[] names = new String[list.size()];
+        for (int i = 0; i < list.size(); i++) {
+            names[i] = name(list.get(i));
+        }
+        Arrays.sort(names);
+        return names;
+    }
+
+    public static String name(Object o1) {
+        if (o1 instanceof RealmRepresentation) {
+            return ((RealmRepresentation) o1).getRealm();
+        } else if (o1 instanceof ApplicationRepresentation) {
+            return ((ApplicationRepresentation) o1).getName();
+        } else if (o1 instanceof OAuthClientRepresentation) {
+            return ((OAuthClientRepresentation) o1).getName();
+        }
+        throw new IllegalArgumentException();
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ApplicationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ApplicationTest.java
new file mode 100644
index 0000000..61a1b9d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ApplicationTest.java
@@ -0,0 +1,47 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Test;
+import org.keycloak.representations.idm.ApplicationRepresentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ApplicationTest extends AbstractClientTest {
+
+    @Test
+    public void getApplications() {
+        assertNames(realm.applications().findAll(), "account", "realm-management", "security-admin-console");
+    }
+
+    @Test
+    public void createApplication() {
+        ApplicationRepresentation rep = new ApplicationRepresentation();
+        rep.setName("my-app");
+        rep.setEnabled(true);
+        realm.applications().create(rep);
+
+        assertNames(realm.applications().findAll(), "account", "realm-management", "security-admin-console", "my-app");
+    }
+
+    @Test
+    public void removeApplication() {
+        createApplication();
+
+        realm.applications().get("my-app").remove();
+    }
+
+    @Test
+    public void getApplicationRepresentation() {
+        createApplication();
+
+        ApplicationRepresentation rep = realm.applications().get("my-app").toRepresentation();
+        assertEquals("my-app", rep.getName());
+        assertTrue(rep.isEnabled());
+    }
+
+
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/OAuthClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/OAuthClientTest.java
new file mode 100644
index 0000000..e66a8ea
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/OAuthClientTest.java
@@ -0,0 +1,50 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.representations.idm.OAuthClientRepresentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthClientTest extends AbstractClientTest {
+
+    @Test
+    public void getOAuthClients() {
+        assertTrue(realm.oAuthClients().findAll().isEmpty());
+    }
+
+    @Test
+    public void createOAuthClient() {
+        OAuthClientRepresentation rep = new OAuthClientRepresentation();
+        rep.setName("my-client");
+        rep.setEnabled(true);
+        realm.oAuthClients().create(rep);
+
+        assertNames(realm.oAuthClients().findAll(), "my-client");
+    }
+
+    @Test
+    @Ignore
+    // TODO For some reason clients are retrieved using id, not client-id
+    public void removeOAuthClient() {
+        createOAuthClient();
+
+        realm.oAuthClients().get("my-client").remove();
+    }
+
+    @Test
+    @Ignore
+    // TODO For some reason clients are retrieved using id, not client-id
+    public void getOAuthClientRepresentation() {
+        createOAuthClient();
+
+        OAuthClientRepresentation rep = realm.oAuthClients().get("my-client").toRepresentation();
+        assertEquals("my-client", rep.getName());
+        assertTrue(rep.isEnabled());
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
new file mode 100644
index 0000000..b0a7c8f
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -0,0 +1,56 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmTest extends AbstractClientTest {
+
+    @Test
+    public void getRealms() {
+        assertNames(keycloak.realms().findAll(), "master", "test", REALM_NAME);
+    }
+
+    @Test
+    public void createRealm() {
+        try {
+            RealmRepresentation rep = new RealmRepresentation();
+            rep.setRealm("new-realm");
+
+            keycloak.realms().create(rep);
+
+            assertNames(keycloak.realms().findAll(), "master", "test", REALM_NAME, "new-realm");
+        } finally {
+            KeycloakSession session = keycloakRule.startSession();
+            RealmManager manager = new RealmManager(session);
+            RealmModel newRealm = manager.getRealmByName("new-realm");
+            if (newRealm != null) {
+                manager.removeRealm(newRealm);
+            }
+            keycloakRule.stopSession(session, true);
+        }
+    }
+
+    @Test
+    public void removeRealm() {
+        realm.remove();
+
+        assertNames(keycloak.realms().findAll(), "master", "test");
+    }
+
+    @Test
+    public void getRealmRepresentation() {
+        RealmRepresentation rep = realm.toRepresentation();
+        assertEquals(REALM_NAME, rep.getRealm());
+        assertTrue(rep.isEnabled());
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
new file mode 100644
index 0000000..d3981c4
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -0,0 +1,39 @@
+package org.keycloak.testsuite.admin;
+
+import org.junit.Test;
+import org.keycloak.representations.idm.UserRepresentation;
+
+import javax.ws.rs.ClientErrorException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserTest extends AbstractClientTest {
+
+    @Test
+    public void createUser() {
+        UserRepresentation user = new UserRepresentation();
+        user.setUsername("user1");
+        user.setEmail("user1@localhost");
+
+        realm.users().create(user);
+    }
+
+    @Test
+    public void createDuplicatedUser() {
+        createUser();
+
+        try {
+            UserRepresentation user = new UserRepresentation();
+            user.setUsername("user1");
+            realm.users().create(user);
+            fail("Expected failure");
+        } catch (ClientErrorException e) {
+            assertEquals(409, e.getResponse().getStatus());
+        }
+    }
+
+}