keycloak-aplcache

Details

diff --git a/core/src/main/java/org/keycloak/OAuthErrorException.java b/core/src/main/java/org/keycloak/OAuthErrorException.java
index 3cddba2..4eff35f 100755
--- a/core/src/main/java/org/keycloak/OAuthErrorException.java
+++ b/core/src/main/java/org/keycloak/OAuthErrorException.java
@@ -19,6 +19,8 @@ public class OAuthErrorException extends Exception {
     }
     public OAuthErrorException(String error, String description, String message) {
         super(message);
+        this.error = error;
+        this.description = description;
     }
     public OAuthErrorException(String error, String description) {
         super(description);
diff --git a/core/src/main/java/org/keycloak/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
index f40d83e..b392ca6 100755
--- a/core/src/main/java/org/keycloak/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/ServiceUrlConstants.java
@@ -9,4 +9,7 @@ public interface ServiceUrlConstants {
     public static final String TOKEN_SERVICE_LOGIN_PATH = "/rest/realms/{realm-name}/tokens/login";
     public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/rest/realms/{realm-name}/tokens/access/codes";
     public static final String TOKEN_SERVICE_REFRESH_PATH = "/rest/realms/{realm-name}/tokens/refresh";
+    public static final String TOKEN_SERVICE_LOGOUT_PATH = "/rest/realms/{realm-name}/tokens/logout";
+    public static final String ACCOUNT_SERVICE_PATH = "/rest/realms/{realm-name}/account";
+
 }
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index 777bfd5..2abf670 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -25,49 +25,14 @@
     <dependencies>
         <dependency>
             <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-core</artifactId>
+            <artifactId>keycloak-installed</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-social-core</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.keycloak</groupId>
-            <artifactId>keycloak-services</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>net.iharder</groupId>
-            <artifactId>base64</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-core-asl</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.codehaus.jackson</groupId>
-            <artifactId>jackson-mapper-asl</artifactId>
-        </dependency>
     </dependencies>
 
     <build>
-        <finalName>customer-portal</finalName>
         <plugins>
             <plugin>
-                <groupId>org.jboss.as.plugins</groupId>
-                <artifactId>jboss-as-maven-plugin</artifactId>
-                <version>7.1.1.Final</version>
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
-            </plugin>
-            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-deploy-plugin</artifactId>
                 <configuration>
diff --git a/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java b/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
index 86dfae1..f04a283 100644
--- a/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
+++ b/examples/demo-template/customer-app-cli/src/main/java/org/keycloak/example/CustomerCli.java
@@ -1,72 +1,84 @@
 package org.keycloak.example;
 
-import org.json.JSONObject;
-import org.keycloak.jose.jws.JWSInput;
-import org.keycloak.services.managers.ApplicationManager;
-import org.keycloak.social.utils.SimpleHttp;
-import org.keycloak.util.JsonSerialization;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.keycloak.adapters.ServerRequest;
+import org.keycloak.adapters.installed.KeycloakInstalled;
 
-import java.awt.*;
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.util.UUID;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class CustomerCli {
 
-    private static String authServerUrl;
-    private static String realm;
-    private static String clientId;
+    public static final ObjectMapper mapper = new ObjectMapper();
 
-    private static String accessToken;
-    private static BufferedReader br;
+    static {
+        mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
+        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
+    }
 
-    public static void main(String[] args) throws Exception {
-        try {
-            String f = args.length > 0 ? args[0] : "keycloak.json";
-            ApplicationManager.InstallationAdapterConfig config = JsonSerialization.readValue(new FileInputStream(new File(f)), ApplicationManager.InstallationAdapterConfig.class);
+    private static BufferedReader br;
 
-            authServerUrl = config.getAuthServerUrl();
-            realm = config.getRealm();
-            clientId = config.getResource();
-        } catch (Throwable t) {
-            System.err.println("Failed to load config:");
-            t.printStackTrace();
-            System.exit(1);
-        }
+    private static KeycloakInstalled keycloak;
 
+    public static void main(String[] args) throws Exception {
+        keycloak = new KeycloakInstalled();
         br = new BufferedReader(new InputStreamReader(System.in));
 
         printHelp();
+        printDivider();
 
+        System.out.print("$ ");
         for (String s = br.readLine(); s != null; s = br.readLine()) {
-            if (s.equals("login")) {
-                login();
+            printDivider();
 
-            } else if (s.equals("login-desktop")) {
-                loginDesktop();
-            } else if (s.equals("login-manual")) {
-                loginManual();
-            } else if (s.equals("profile")) {
-                profile();
-            } else if (s.equals("token")) {
-                token();
-            } else if (s.equals("exit")) {
-                System.exit(0);
-            } else {
-                printHelp();
+            try {
+                if (s.equals("login")) {
+                    keycloak.login(System.out, br);
+                    System.out.println("Logged in: " + keycloak.getToken().getSubject());
+                } else if (s.equals("logout")) {
+                    keycloak.logout();
+                    System.out.println("Logged out");
+                } else if (s.equals("login-desktop")) {
+                    keycloak.loginDesktop();
+                    System.out.println("Logged in: " + keycloak.getToken().getSubject());
+                } else if (s.equals("login-manual")) {
+                    keycloak.loginManual(System.out, br);
+                    System.out.println("Logged in: " + keycloak.getToken().getSubject());
+                } else if (s.equals("profile")) {
+                    profile();
+                } else if (s.equals("customers")) {
+                    customers();
+                } else if (s.equals("token")) {
+                    System.out.println(mapper.writeValueAsString(keycloak.getToken()));
+                } else if (s.equals("id-token")) {
+                    System.out.println(mapper.writeValueAsString(keycloak.getIdToken()));
+                } else if (s.equals("refresh")) {
+                    keycloak.refreshToken();
+                    System.out.println("Token refreshed: expires at " + new Date(keycloak.getToken().getExpiration() * 1000));
+                } else if (s.equals("exit")) {
+                    System.exit(0);
+                } else {
+                    printHelp();
+                }
+            } catch (ServerRequest.HttpFailure t) {
+                System.out.println(t.getError());
+            }catch (Throwable t) {
+                System.out.println(t.getMessage() != null ? t.getMessage() : t.getClass().toString());
             }
+            printDivider();
+
+            System.out.print("$ ");
         }
     }
 
@@ -75,181 +87,50 @@ public class CustomerCli {
         System.out.println("  login - login with desktop browser if available, otherwise do manual login");
         System.out.println("  login-manual - manual login");
         System.out.println("  login-desktop - desktop login");
-        System.out.println("  profile - retrieve user profile");
         System.out.println("  token - show token details");
+        System.out.println("  id-token - show ID token details");
+        System.out.println("  profile - retrieve user profile");
+        System.out.println("  customers - retrieve customers listing");
+        System.out.println("  refresh - refresh token");
         System.out.println("  exit - exit");
-
     }
 
-    public static void login() {
-        try {
-            if (Desktop.isDesktopSupported()) {
-                loginDesktop();
-            } else {
-                loginManual();
-            }
-
-        } catch (Throwable e) {
-            System.err.println("Failed to log in user: " + e.getMessage());
-        }
+    public static void printDivider() {
+        System.out.println("");
     }
 
-    public static void loginDesktop() {
-        try {
-            CallbackListener callback = new CallbackListener();
-            callback.start();
-
-            String redirectUri = URLEncoder.encode("http://localhost:" + callback.getPort(), "utf-8");
-            String state = UUID.randomUUID().toString();
-
-            String loginUrl = authServerUrl + "/rest/realms/" + realm + "/tokens/login?" +
-                    "client_id=" + clientId +
-                    "&redirect_uri=" + redirectUri +
-                    "&state=" + state;
-
-            Desktop.getDesktop().browse(new URI(loginUrl));
-
-            callback.join();
-
-            if (!state.equals(callback.getStateParam())) {
-                System.err.println("Invalid state received");
-                return;
-            }
-
-            if (callback.getError() != null) {
-                System.err.println("Failed to login: " + callback.getError());
-                return;
-            }
-
-            System.out.println("User logged in");
-
-            String tokenUrl = authServerUrl + "/rest/realms/" + realm + "/tokens/access/codes";
-            JSONObject response = SimpleHttp.doPost(tokenUrl).param("client_id", clientId).param("code", callback.getCode()).asJson();
-            accessToken = response.getString("access_token");
-            System.out.println("Access token received");
-
-            return;
-        } catch (Throwable e) {
-            System.err.println("Failed to log in user: " + e.getMessage());
+    public static void profile() throws Exception {
+        String accountUrl = keycloak.getDeployment().getAccountUrl();
+        HttpGet get = new HttpGet(accountUrl);
+        get.setHeader("Accept", "application/json");
+        get.setHeader("Authorization", "Bearer " + keycloak.getTokenString(10, TimeUnit.SECONDS));
+
+        HttpResponse response = keycloak.getDeployment().getClient().execute(get);
+        if (response.getStatusLine().getStatusCode() == 200) {
+            print(response.getEntity().getContent());
+        } else {
+            System.out.println(response.getStatusLine().toString());
         }
     }
 
-    public static void loginManual() {
-        try {
-            CallbackListener callback = new CallbackListener();
-            callback.start();
-
-            String redirectUri = URLEncoder.encode("urn:ietf:wg:oauth:2.0:oob", "utf-8");
-
-            String loginUrl = authServerUrl + "/rest/realms/" + realm + "/tokens/login?" +
-                    "client_id=" + clientId +
-                    "&redirect_uri=" + redirectUri;
-
-            System.out.println("Open the following URL in a browser and paste the code back:");
-            System.out.println(loginUrl);
-            System.out.print("code: ");
-
-            String code = br.readLine().trim();
-
-            String tokenUrl = authServerUrl + "/rest/realms/" + realm + "/tokens/access/codes";
-            JSONObject response = SimpleHttp.doPost(tokenUrl).param("client_id", clientId).param("code", code).asJson();
-            accessToken = response.getString("access_token");
-            System.out.println("Access token received");
-
-            return;
-        } catch (Throwable e) {
-            System.err.println("Failed to log in user: " + e.getMessage());
+    public static void customers() throws Exception {
+        String customersUrl = "http://localhost:8080/database/customers";
+        HttpGet get = new HttpGet(customersUrl);
+        get.setHeader("Accept", "application/json");
+        get.setHeader("Authorization", "Bearer " + keycloak.getTokenString(10, TimeUnit.SECONDS));
+
+        HttpResponse response = keycloak.getDeployment().getClient().execute(get);
+        if (response.getStatusLine().getStatusCode() == 200) {
+            print(response.getEntity().getContent());
+        } else {
+            System.out.println(response.getStatusLine().toString());
         }
     }
 
-    public static void profile() {
-        try {
-            String profileUrl = authServerUrl + "/rest/realms/" + realm + "/account";
-            JSONObject profile = SimpleHttp.doGet(profileUrl).header("Accept", "application/json").header("Authorization", "Bearer " + accessToken).asJson();
-            System.out.println(profile.toString(2));
-        } catch (Throwable e) {
-            System.err.println("Failed: " + e.getMessage());
-        }
-    }
-
-    public static void token() {
-        try {
-            JWSInput jws = new JWSInput(accessToken);
-            System.out.println(new JSONObject(new String(jws.getContent())).toString(2));
-        } catch (Throwable e) {
-            System.err.println("Failed to log in user: " + e.getMessage());
-        }
-    }
-
-    public static class CallbackListener extends Thread {
-
-        private String code;
-
-        private String error;
-
-        private String state;
-        private final ServerSocket server;
-
-        public CallbackListener() throws IOException {
-            server = new ServerSocket(0);
-        }
-
-        public int getPort() {
-            return server.getLocalPort();
-        }
-
-        @Override
-        public void run() {
-            try {
-                Socket socket = server.accept();
-
-                System.out.println("Request received");
-
-                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
-                String request = br.readLine();
-
-                System.out.println(request);
-
-                String[] params = request.split(" ")[1].substring(2).split("&");
-                System.out.println(params.length);
-
-                for (String param : params) {
-                    String[] p = param.split("=");
-                    System.out.println(p[0]);
-                    if (p[0].equals("code")) {
-                        code = p[1];
-                    } else if (p[0].equals("error")) {
-                        error = p[1];
-                    } else if (p[0].equals("state")) {
-                        state = p[1];
-                    }
-                }
-
-                PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
-                pw.println("Please close and return to application");
-                pw.flush();
-
-                socket.close();
-            } catch (IOException e) {
-                error = "Local error: " + e.getMessage();
-            }
-
-            try {
-                server.close();
-            } catch (IOException e) {
-            }
-        }
-
-        public String getCode() throws InterruptedException {
-            return code;
-        }
-
-        public String getError() {
-            return error;
-        }
-
-        public String getStateParam() {
-            return state;
+    private static void print(InputStream is) throws IOException {
+        BufferedReader br = new BufferedReader(new InputStreamReader(is));
+        for (String l = br.readLine(); l != null; l = br.readLine()) {
+            System.out.println(l);
         }
     }
 
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index ba969bd..bd2eabc 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -17,6 +17,8 @@ public class KeycloakDeployment {
     protected KeycloakUriBuilder authUrl;
     protected String codeUrl;
     protected String refreshUrl;
+    protected KeycloakUriBuilder logoutUrl;
+    protected String accountUrl;
 
     protected String resourceName;
     protected boolean bearerOnly;
@@ -194,4 +196,20 @@ public class KeycloakDeployment {
     public void setNotBefore(int notBefore) {
         this.notBefore = notBefore;
     }
+
+    public KeycloakUriBuilder getLogoutUrl() {
+        return logoutUrl;
+    }
+
+    public void setLogoutUrl(KeycloakUriBuilder logoutUrl) {
+        this.logoutUrl = logoutUrl;
+    }
+
+    public String getAccountUrl() {
+        return accountUrl;
+    }
+
+    public void setAccountUrl(String accountUrl) {
+        this.accountUrl = accountUrl;
+    }
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index d497c0d..3f5a4a4 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -63,14 +63,14 @@ public class KeycloakDeploymentBuilder {
         String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString();
         String tokenUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
         String refreshUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
-
-
+        String logoutUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(adapterConfig.getRealm()).toString();
+        String accountUrl = serverBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(adapterConfig.getRealm()).toString();
 
         deployment.setAuthUrl(KeycloakUriBuilder.fromUri(authUrl).queryParam("client_id", deployment.getResourceName()));
         deployment.setCodeUrl(tokenUrl);
         deployment.setRefreshUrl(refreshUrl);
-
-
+        deployment.setLogoutUrl(KeycloakUriBuilder.fromUri(logoutUrl));
+        deployment.setAccountUrl(accountUrl);
 
         return deployment;
     }
diff --git a/integration/installed/pom.xml b/integration/installed/pom.xml
new file mode 100755
index 0000000..78860ce
--- /dev/null
+++ b/integration/installed/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-installed</artifactId>
+    <name>Keycloak Installed Application</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk16</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${keycloak.apache.httpcomponents.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>net.iharder</groupId>
+            <artifactId>base64</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-core-asl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
new file mode 100644
index 0000000..c0f7c27
--- /dev/null
+++ b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -0,0 +1,312 @@
+package org.keycloak.adapters.installed;
+
+import org.keycloak.OAuthErrorException;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.ServerRequest;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.representations.IDToken;
+
+import java.awt.*;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class KeycloakInstalled {
+
+    private static final String KEYCLOAK_JSON = "META-INF/keycloak.json";
+
+    private KeycloakDeployment deployment;
+
+    private enum Status {
+        LOGGED_MANUAL, LOGGED_DESKTOP
+    }
+
+    private String tokenString;
+    private String idTokenString;
+    private IDToken idToken;
+    private AccessToken token;
+    private String refreshToken;
+    private Status status;
+
+    public KeycloakInstalled() {
+        InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
+        deployment = KeycloakDeploymentBuilder.build(config);
+    }
+
+    public KeycloakInstalled(InputStream config) {
+        deployment = KeycloakDeploymentBuilder.build(config);
+    }
+
+    public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
+        if (isDesktopSupported()) {
+            loginDesktop();
+        } else {
+            loginManual();
+        }
+    }
+
+    public void login(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
+        if (isDesktopSupported()) {
+            loginDesktop();
+        } else {
+            loginManual(printer, reader);
+        }
+    }
+
+    public void logout() throws IOException, InterruptedException, URISyntaxException {
+        if (status == Status.LOGGED_DESKTOP) {
+            logoutDesktop();
+        }
+
+        tokenString = null;
+        token = null;
+
+        idTokenString = null;
+        idToken = null;
+
+        refreshToken = null;
+
+        status = null;
+    }
+
+    public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException {
+        CallbackListener callback = new CallbackListener();
+        callback.start();
+
+        String redirectUri = "http://localhost:" + callback.server.getLocalPort();
+        String state = UUID.randomUUID().toString();
+
+        String authUrl = deployment.getAuthUrl().clone()
+                .queryParam("client_id", deployment.getResourceName())
+                .queryParam("redirect_uri", redirectUri)
+                .queryParam("state", state)
+                .queryParam("login", "true")
+                .build().toString();
+
+        Desktop.getDesktop().browse(new URI(authUrl));
+
+        callback.join();
+
+        if (!state.equals(callback.state)) {
+            throw new VerificationException("Invalid state");
+        }
+
+        if (callback.error != null) {
+            throw new OAuthErrorException(callback.error, callback.errorDescription);
+        }
+
+        if (callback.errorException != null) {
+            throw callback.errorException;
+        }
+
+        processCode(callback.code, redirectUri);
+
+        status = Status.LOGGED_DESKTOP;
+    }
+
+    private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException {
+        CallbackListener callback = new CallbackListener();
+        callback.start();
+
+        String redirectUri = "http://localhost:" + callback.server.getLocalPort();
+
+        String logoutUrl = deployment.getLogoutUrl()
+                .queryParam("redirect_uri", redirectUri)
+                .build().toString();
+
+        Desktop.getDesktop().browse(new URI(logoutUrl));
+
+        callback.join();
+
+        if (callback.errorException != null) {
+            throw callback.errorException;
+        }
+    }
+
+    public void loginManual() throws IOException, ServerRequest.HttpFailure, VerificationException {
+        loginManual(System.out, new InputStreamReader(System.in));
+    }
+
+    public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException {
+        CallbackListener callback = new CallbackListener();
+        callback.start();
+
+        String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
+
+        String authUrl = deployment.getAuthUrl().clone()
+                .queryParam("client_id", deployment.getResourceName())
+                .queryParam("redirect_uri", redirectUri)
+                .queryParam("login", "true")
+                .build().toString();
+
+        printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
+        printer.println(authUrl);
+        printer.println();
+        printer.print("Code: ");
+
+        String code = readCode(reader);
+        processCode(code, redirectUri);
+
+        status = Status.LOGGED_MANUAL;
+    }
+
+    public String getTokenString() throws VerificationException, IOException, ServerRequest.HttpFailure {
+        return tokenString;
+    }
+
+    public String getTokenString(long minValidity, TimeUnit unit) throws VerificationException, IOException, ServerRequest.HttpFailure {
+        long expires = token.getExpiration() * 1000 - unit.toMillis(minValidity);
+        if (expires < System.currentTimeMillis()) {
+            refreshToken();
+        }
+
+        return tokenString;
+    }
+
+    public void refreshToken() throws IOException, ServerRequest.HttpFailure, VerificationException {
+        AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
+        parseAccessToken(tokenResponse);
+    }
+
+    private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException {
+        tokenString = tokenResponse.getToken();
+        refreshToken = tokenResponse.getRefreshToken();
+        idTokenString = tokenResponse.getIdToken();
+
+        token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealm());
+        if (idTokenString != null) {
+            JWSInput input = new JWSInput(idTokenString);
+            try {
+                idToken = input.readJsonContent(IDToken.class);
+            } catch (IOException e) {
+                throw new VerificationException();
+            }
+        }
+    }
+
+    public AccessToken getToken() {
+        return token;
+    }
+
+    public IDToken getIdToken() {
+        return idToken;
+    }
+
+    public String getIdTokenString() {
+        return idTokenString;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public boolean isDesktopSupported() {
+        return Desktop.isDesktopSupported();
+    }
+
+    public KeycloakDeployment getDeployment() {
+        return deployment;
+    }
+
+    private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
+        AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri);
+        parseAccessToken(tokenResponse);
+    }
+
+    private String readCode(Reader reader) throws IOException {
+        StringBuilder sb = new StringBuilder();
+
+        char cb[] = new char[1];
+        while (reader.read(cb) != -1) {
+            char c = cb[0];
+            if ((c == ' ') || (c == '\n') || (c == '\r')) {
+                break;
+            } else {
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    public class CallbackListener extends Thread {
+
+        private ServerSocket server;
+
+        private String code;
+
+        private String error;
+
+        private String errorDescription;
+
+        private IOException errorException;
+
+        private String state;
+
+        public CallbackListener() throws IOException {
+            server = new ServerSocket(0);
+        }
+
+        @Override
+        public void run() {
+            try {
+                Socket socket = server.accept();
+
+                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+                String request = br.readLine();
+
+                String url = request.split(" ")[1];
+                if (url.indexOf('?') >= 0) {
+                    url = url.split("\\?")[1];
+                    String[] params = url.split("&");
+
+                    for (String param : params) {
+                        String[] p = param.split("=");
+                        if (p[0].equals("code")) {
+                            code = p[1];
+                        } else if (p[0].equals("error")) {
+                            error = p[1];
+                        } else if (p[0].equals("error-description")) {
+                            errorDescription = p[1];
+                        } else if (p[0].equals("state")) {
+                            state = p[1];
+                        }
+                    }
+                }
+
+                PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
+                pw.println("Please close window and return to application");
+                pw.flush();
+
+                socket.close();
+            } catch (IOException e) {
+                errorException = e;
+            }
+
+            try {
+                server.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+}
diff --git a/integration/pom.xml b/integration/pom.xml
index 1295151..5904823 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -25,6 +25,7 @@
         <module>wildfly-subsystem</module>
         <module>as7-eap-subsystem</module>
         <module>js</module>
+        <module>installed</module>
         <!-- <module>as7-eap6/jboss-modules</module> -->
     </modules>
 </project>