keycloak-memoizeit

Work in progress...

6/25/2014 9:29:38 AM

Details

diff --git a/testsuite/performance-web/pom.xml b/testsuite/performance-web/pom.xml
index 7ca6bac..8cf6f19 100644
--- a/testsuite/performance-web/pom.xml
+++ b/testsuite/performance-web/pom.xml
@@ -17,11 +17,25 @@
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-testsuite-integration</artifactId>
             <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.jboss.spec.javax.servlet</groupId>
+                    <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
             <artifactId>keycloak-testsuite-tools</artifactId>
             <version>${project.version}</version>
+            <type>war</type>
+        </dependency>
+
+        <!-- Needed by undertow -->
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+            <version>1.0.0.Final</version>
         </dependency>
 
         <!-- Resteasy deps specified here as we want latest version of them -->
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
index 55cc44f..b151019 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/KeycloakPerfServer.java
@@ -4,9 +4,11 @@ import java.io.InputStream;
 
 import javax.servlet.DispatcherType;
 
+import io.undertow.server.handlers.resource.ClassPathResourceManager;
 import io.undertow.servlet.Servlets;
 import io.undertow.servlet.api.DeploymentInfo;
 import io.undertow.servlet.api.FilterInfo;
+import io.undertow.servlet.api.MimeMapping;
 import io.undertow.servlet.api.ServletInfo;
 import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
 import org.jboss.resteasy.spi.ResteasyDeployment;
@@ -76,10 +78,12 @@ public class KeycloakPerfServer {
         deploymentInfo.setContextPath("/perf-app");
 
         ServletInfo servlet = new ServletInfo("PerfAppServlet", PerfAppServlet.class);
-        servlet.addMapping("/*");
+        servlet.addMapping("/perf-servlet");
 
         deploymentInfo.addServlet(servlet);
 
+        deploymentInfo.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader()));
+
         keycloakServer.getServer().deploy(deploymentInfo);
 
         System.out.println("PerfApp deployed");
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java
new file mode 100644
index 0000000..bb6e0c1
--- /dev/null
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java
@@ -0,0 +1,376 @@
+package org.keycloak.testsuite.performance.web;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.jboss.resteasy.security.PemUtils;
+import org.json.JSONObject;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+import org.keycloak.services.resources.TokenService;
+import org.keycloak.util.BasicAuthHelper;
+
+/**
+ * TODO: Remove from here and instead merge with org.keycloak.testsuite.OAuthClient
+ *
+ *@author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthClient {
+
+    private String baseUrl = "http://localhost:8081/auth";
+
+    private String realm = "perf-realm";
+
+    private String responseType = OAuth2Constants.CODE;
+
+    private String grantType = "authorization_code";
+
+    private String clientId = "perf-app";
+
+    private String redirectUri = "http://localhost:8081/perf-app/perf-servlet";
+
+    private String state = "123";
+
+    private PublicKey realmPublicKey;
+
+    public OAuthClient() {
+        try {
+            JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/perfrealm.json")));
+            realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to retrieve realm public key", e);
+        }
+    }
+
+    public AccessTokenResponse doAccessTokenRequest(String code, String password) {
+        HttpClient client = new DefaultHttpClient();
+        HttpPost post = new HttpPost(getAccessTokenUrl());
+
+        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+        if (grantType != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
+        }
+        if (code != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
+        }
+        if (redirectUri != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
+        }
+        if (clientId != null && password != null) {
+            String authorization = BasicAuthHelper.createHeader(clientId, password);
+            post.setHeader("Authorization", authorization);
+        }
+        else if (clientId != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
+        }
+
+        UrlEncodedFormEntity formEntity = null;
+        try {
+            formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        post.setEntity(formEntity);
+
+        try {
+            return new AccessTokenResponse(client.execute(post));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to retrieve access token", e);
+        }
+    }
+
+    public AccessTokenResponse doGrantAccessTokenRequest(String clientSecret, String username,  String password) throws Exception {
+        HttpClient client = new DefaultHttpClient();
+        HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl());
+
+        String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
+        post.setHeader("Authorization", authorization);
+
+        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+        parameters.add(new BasicNameValuePair("username", username));
+        parameters.add(new BasicNameValuePair("password", password));
+
+        UrlEncodedFormEntity formEntity;
+        try {
+            formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        post.setEntity(formEntity);
+
+        return new AccessTokenResponse(client.execute(post));
+    }
+
+    public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException {
+        HttpClient client = new DefaultHttpClient();
+        HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState));
+
+        return client.execute(get);
+    }
+
+    public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) {
+        HttpClient client = new DefaultHttpClient();
+        HttpPost post = new HttpPost(getRefreshTokenUrl());
+
+        List<NameValuePair> parameters = new LinkedList<NameValuePair>();
+        if (grantType != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType));
+        }
+        if (refreshToken != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
+        }
+        if (clientId != null && password != null) {
+            String authorization = BasicAuthHelper.createHeader(clientId, password);
+            post.setHeader("Authorization", authorization);
+        }
+        else if (clientId != null) {
+            parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId));
+        }
+
+        UrlEncodedFormEntity formEntity;
+        try {
+            formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        post.setEntity(formEntity);
+
+        try {
+            return new AccessTokenResponse(client.execute(post));
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to retrieve access token", e);
+        }
+    }
+
+    public AccessToken verifyToken(String token) {
+        try {
+            return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
+        } catch (VerificationException e) {
+            throw new RuntimeException("Failed to verify token", e);
+        }
+    }
+
+    public void verifyCode(String code) {
+        if (!RSAProvider.verify(new JWSInput(code), realmPublicKey)) {
+            throw new RuntimeException("Failed to verify code");
+        }
+    }
+
+    public RefreshToken verifyRefreshToken(String refreshToken) {
+        try {
+            JWSInput jws = new JWSInput(refreshToken);
+            if (!RSAProvider.verify(jws, realmPublicKey)) {
+                throw new RuntimeException("Invalid refresh token");
+            }
+            return jws.readJsonContent(RefreshToken.class);
+        } catch (Exception e) {
+            throw new RuntimeException("Invalid refresh token", e);
+        }
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public String getLoginFormUrl() {
+        UriBuilder b = TokenService.loginPageUrl(UriBuilder.fromUri(baseUrl));
+        if (responseType != null) {
+            b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType);
+        }
+        if (clientId != null) {
+            b.queryParam(OAuth2Constants.CLIENT_ID, clientId);
+        }
+        if (redirectUri != null) {
+            b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+        }
+        if (state != null) {
+            b.queryParam(OAuth2Constants.STATE, state);
+        }
+        return b.build(realm).toString();
+    }
+
+    public String getAccessTokenUrl() {
+        UriBuilder b = TokenService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl));
+        return b.build(realm).toString();
+    }
+
+    public String getLogoutUrl(String redirectUri, String sessionState) {
+        UriBuilder b = TokenService.logoutUrl(UriBuilder.fromUri(baseUrl));
+        if (redirectUri != null) {
+            b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
+        }
+        if (sessionState != null) {
+            b.queryParam("session_state", sessionState);
+        }
+        return b.build(realm).toString();
+    }
+
+    public String getResourceOwnerPasswordCredentialGrantUrl() {
+        UriBuilder b = TokenService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl));
+        return b.build(realm).toString();
+    }
+
+    public String getRefreshTokenUrl() {
+        UriBuilder b = TokenService.refreshUrl(UriBuilder.fromUri(baseUrl));
+        return b.build(realm).toString();
+    }
+
+    public OAuthClient realm(String realm) {
+        this.realm = realm;
+        return this;
+    }
+    public OAuthClient realmPublicKey(PublicKey key) {
+        this.realmPublicKey = key;
+        return this;
+    }
+
+    public OAuthClient clientId(String clientId) {
+        this.clientId = clientId;
+        return this;
+    }
+
+    public OAuthClient redirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+        return this;
+    }
+
+    public OAuthClient responseType(String responseType) {
+        this.responseType = responseType;
+        return this;
+    }
+
+    public OAuthClient state(String state) {
+        this.state = state;
+        return this;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public static class AuthorizationCodeResponse {
+
+        private String code;
+        private String state;
+        private String error;
+
+        public AuthorizationCodeResponse(OAuthClient client, HttpServletRequest req) {
+            code = req.getParameter(OAuth2Constants.CODE);
+            state = req.getParameter(OAuth2Constants.STATE);
+            error = req.getParameter(OAuth2Constants.ERROR);
+        }
+
+        public String getCode() {
+            return code;
+        }
+
+        public String getState() {
+            return state;
+        }
+
+        public String getError() {
+            return error;
+        }
+
+    }
+
+    public static class AccessTokenResponse {
+        private int statusCode;
+
+        private String accessToken;
+        private String tokenType;
+        private int expiresIn;
+        private String refreshToken;
+        private String idToken;
+        private String sessionState;
+
+        private String error;
+
+        public AccessTokenResponse(HttpResponse response) throws Exception {
+            statusCode = response.getStatusLine().getStatusCode();
+            if (!"application/json".equals(response.getHeaders("Content-Type")[0].getValue())) {
+                throw new RuntimeException("Invalid content type");
+            }
+
+            String s = IOUtils.toString(response.getEntity().getContent());
+            JSONObject responseJson = new JSONObject(s);
+
+            if (statusCode == 200) {
+                accessToken = responseJson.getString("access_token");
+                tokenType = responseJson.getString("token_type");
+                expiresIn = responseJson.getInt("expires_in");
+                idToken = responseJson.optString("id_token");
+                sessionState = responseJson.optString("session-state");
+
+                if (responseJson.has(OAuth2Constants.REFRESH_TOKEN)) {
+                    refreshToken = responseJson.getString(OAuth2Constants.REFRESH_TOKEN);
+                }
+            } else {
+                error = responseJson.getString(OAuth2Constants.ERROR);
+            }
+        }
+
+        public String getAccessToken() {
+            return accessToken;
+        }
+
+        public String getError() {
+            return error;
+        }
+
+        public int getExpiresIn() {
+            return expiresIn;
+        }
+
+        public int getStatusCode() {
+            return statusCode;
+        }
+
+        public String getRefreshToken() {
+            return refreshToken;
+        }
+
+        public String getTokenType() {
+            return tokenType;
+        }
+
+        public String getIdToken() {
+            return idToken;
+        }
+
+        public String getSessionState() {
+            return sessionState;
+        }
+    }
+
+
+}
diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
index cc38ae6..6de11f9 100644
--- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
+++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/PerfAppServlet.java
@@ -1,39 +1,115 @@
 package org.keycloak.testsuite.performance.web;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.RefreshToken;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class PerfAppServlet extends HttpServlet {
 
+    private Template indexTemplate;
+    private OAuthClient oauthClient;
+
+    @Override
+    public void init() throws ServletException {
+        try {
+            Configuration cfg = new Configuration();
+            cfg.setTemplateLoader(new ClassTemplateLoader(getClass(), "/"));
+            indexTemplate = cfg.getTemplate("perf-app-resources/index.ftl");
+
+            oauthClient = new OAuthClient();
+        } catch (IOException ioe) {
+            throw new ServletException(ioe);
+        }
+    }
+
     @Override
     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        String resourcePath = "perf-app-resources" + req.getPathInfo();
-        System.out.println("Resource path: " + resourcePath);
-
-        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resourcePath);
-        if (inputStream == null) {
-            resp.getWriter().println("Not found: " + resourcePath);
-        } else {
-            OutputStream servletOutputStream = resp.getOutputStream();
-
-            byte[] buf = new byte[1024];
-            int bytesRead = 0;
-            while (bytesRead != -1) {
-                bytesRead = inputStream.read(buf);
-                if (bytesRead != -1) {
-                    servletOutputStream.write(buf, 0, bytesRead);
-                }
+        resp.setContentType("text/html");
+        String action = req.getParameter("action");
+
+        if (action != null) {
+            if (action.equals("code")) {
+                keycloakLoginRedirect(req, resp);
+                return;
+            } else if (action.equals("exchangeCode")) {
+                exchangeCodeForToken(req, resp);
+            } else if (action.equals("refresh")) {
+                refreshToken(req, resp);
+            } else if (action.equals("logout")) {
+                logoutRedirect(req, resp);
+                return;
             }
-            servletOutputStream.flush();
+        }
+
+        String code = req.getParameter("code");
+        if (code != null) {
+            req.getSession().setAttribute("code", code);
+        }
+
+        String freemarkerRedirect = freemarkerRedirect(req, resp);
+        resp.getWriter().println(freemarkerRedirect);
+        resp.getWriter().flush();
+    }
+
+    protected void keycloakLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String loginUrl = oauthClient.getLoginFormUrl();
+        resp.sendRedirect(loginUrl);
+    }
+
+    protected void exchangeCodeForToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String code = (String)req.getSession().getAttribute("code");
+        OAuthClient.AccessTokenResponse atResponse = oauthClient.doAccessTokenRequest(code, "password");
+
+        String accessToken = atResponse.getAccessToken();
+        String refreshToken = atResponse.getRefreshToken();
+        req.getSession().setAttribute("accessToken", accessToken);
+        req.getSession().setAttribute("refreshToken", refreshToken);
+    }
+
+    protected void refreshToken(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String refreshToken = (String)req.getSession().getAttribute("refreshToken");
+        OAuthClient.AccessTokenResponse atResponse = oauthClient.doRefreshTokenRequest(refreshToken, "password");
+
+        String accessToken = atResponse.getAccessToken();
+        refreshToken = atResponse.getRefreshToken();
+        req.getSession().setAttribute("accessToken", accessToken);
+        req.getSession().setAttribute("refreshToken", refreshToken);
+    }
+
+    protected void logoutRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+
+    }
+
+    private String freemarkerRedirect(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        Map<String, Object> attributes = new HashMap<String, Object>();
+        attributes.put("requestURI", req.getRequestURI());
+        attributes.put("code",  req.getSession().getAttribute("code"));
+        attributes.put("accessToken",  req.getSession().getAttribute("accessToken"));
+        attributes.put("refreshToken",  req.getSession().getAttribute("refreshToken"));
+
+        try {
+            Writer out = new StringWriter();
+            indexTemplate.process(attributes, out);
+            return out.toString();
+        } catch (TemplateException te) {
+            throw new ServletException(te);
         }
     }
 }
diff --git a/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
new file mode 100644
index 0000000..b8e92eb
--- /dev/null
+++ b/testsuite/performance-web/src/main/resources/perf-app-resources/index.ftl
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+  <head>
+    <title>PerfTest</title>
+    <script>
+      function updateElementWithToken(tokenStr, elementId) {
+        if (tokenStr && tokenStr != "") {
+          var tokenParsed = JSON.stringify(JSON.parse(decodeURIComponent(escape(window.atob( tokenStr.split('.')[1] )))), null, " ");
+          document.getElementById(elementId).innerHTML = tokenParsed;
+        }
+      }
+    </script>
+  </head>
+  <body>
+
+    <p><a href="${requestURI}?action=code">Login and get code</a> | <a href="${requestURI}?action=exchangeCode">Exchange code</a> | <a
+        href="${requestURI}?action=refresh">Refresh token</a> | <a href="${requestURI}?action=logout">Logout</a>
+    </p>
+
+    <p>
+      <#if code??>
+        Code: ${code}
+        <hr />
+      </#if>
+
+      <#if accessToken??>
+        <b>accessToken: </b> ${accessToken}
+        <br />
+        <pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="accessTokenParsed"></pre>
+        <hr />
+
+        <script>
+          updateElementWithToken("${accessToken}", "accessTokenParsed");
+        </script>
+      </#if>
+
+      <#if refreshToken??>
+        <b>refreshToken:  </b> ${refreshToken}
+        <br />
+        <pre style="background-color: #ddd; border: 1px solid #ccc; padding: 10px;" id="refreshTokenParsed"></pre>
+        <hr />
+
+        <script>
+          updateElementWithToken("${refreshToken}", "refreshTokenParsed");
+        </script>
+      </#if>
+
+    </p>
+    <br><br>
+  </body>
+</html>
\ No newline at end of file