keycloak-uncached

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index fe8dfdc..2757b36 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -170,7 +170,7 @@ public class OAuthRequestAuthenticator {
         KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
                 .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
                 .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
-                .queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
+                .queryParam(OAuth2Constants.REDIRECT_URI, url)
                 .queryParam(OAuth2Constants.STATE, state)
                 .queryParam("login", "true");
         if(loginHint != null && loginHint.length() > 0){
diff --git a/common/src/main/java/org/keycloak/common/util/Encode.java b/common/src/main/java/org/keycloak/common/util/Encode.java
index 63b8f36..b195362 100755
--- a/common/src/main/java/org/keycloak/common/util/Encode.java
+++ b/common/src/main/java/org/keycloak/common/util/Encode.java
@@ -24,6 +24,7 @@ import java.nio.ByteBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetDecoder;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -36,7 +37,7 @@ import java.util.regex.Pattern;
  */
 public class Encode
 {
-   private static final String UTF_8 = "UTF-8";
+   private static final String UTF_8 = StandardCharsets.UTF_8.name();
 
    private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");
 
@@ -84,9 +85,7 @@ public class Encode
             case '@':
                continue;
          }
-         StringBuffer sb = new StringBuffer();
-         sb.append((char) i);
-         pathEncoding[i] = URLEncoder.encode(sb.toString());
+         pathEncoding[i] = URLEncoder.encode(String.valueOf((char) i));
       }
       pathEncoding[' '] = "%20";
       System.arraycopy(pathEncoding, 0, matrixParameterEncoding, 0, pathEncoding.length);
@@ -119,9 +118,7 @@ public class Encode
                queryNameValueEncoding[i] = "+";
                continue;
          }
-         StringBuffer sb = new StringBuffer();
-         sb.append((char) i);
-         queryNameValueEncoding[i] = URLEncoder.encode(sb.toString());
+         queryNameValueEncoding[i] = URLEncoder.encode(String.valueOf((char) i));
       }
 
       /*
@@ -159,9 +156,7 @@ public class Encode
                queryStringEncoding[i] = "%20";
                continue;
          }
-         StringBuffer sb = new StringBuffer();
-         sb.append((char) i);
-         queryStringEncoding[i] = URLEncoder.encode(sb.toString());
+         queryStringEncoding[i] = URLEncoder.encode(String.valueOf((char) i));
       }
    }
 
@@ -194,7 +189,7 @@ public class Encode
     */
    public static String encodeFragment(String value)
    {
-      return encodeValue(value, queryNameValueEncoding);
+      return encodeValue(value, queryStringEncoding);
    }
 
    /**
@@ -221,18 +216,19 @@ public class Encode
    public static String decodePath(String path)
    {
       Matcher matcher = encodedCharsMulti.matcher(path);
-      StringBuffer buf = new StringBuffer();
+      int start=0;
+      StringBuilder builder = new StringBuilder();
       CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
       while (matcher.find())
       {
+    	 builder.append(path, start, matcher.start());
          decoder.reset();
          String decoded = decodeBytes(matcher.group(1), decoder);
-         decoded = decoded.replace("\\", "\\\\");
-         decoded = decoded.replace("$", "\\$");
-         matcher.appendReplacement(buf, decoded);
+         builder.append(decoded);
+         start = matcher.end();
       }
-      matcher.appendTail(buf);
-      return buf.toString();
+      builder.append(path, start, path.length());
+      return builder.toString();
    }
 
    private static String decodeBytes(String enc, CharsetDecoder decoder)
@@ -264,7 +260,7 @@ public class Encode
    public static String encodeNonCodes(String string)
    {
       Matcher matcher = nonCodes.matcher(string);
-      StringBuffer buf = new StringBuffer();
+      StringBuilder builder = new StringBuilder();
 
 
       // FYI: we do not use the no-arg matcher.find()
@@ -276,29 +272,32 @@ public class Encode
       while (matcher.find(idx))
       {
          int start = matcher.start();
-         buf.append(string.substring(idx, start));
-         buf.append("%25");
+         builder.append(string.substring(idx, start));
+         builder.append("%25");
          idx = start + 1;
       }
-      buf.append(string.substring(idx));
-      return buf.toString();
+      builder.append(string.substring(idx));
+      return builder.toString();
    }
 
-   private static boolean savePathParams(String segment, StringBuffer newSegment, List<String> params)
+   public static boolean savePathParams(String segment, StringBuilder newSegment, List<String> params)
    {
       boolean foundParam = false;
       // Regular expressions can have '{' and '}' characters.  Replace them to do match
       segment = PathHelper.replaceEnclosedCurlyBraces(segment);
       Matcher matcher = PathHelper.URI_TEMPLATE_PATTERN.matcher(segment);
+      int start = 0;
       while (matcher.find())
       {
+    	 newSegment.append(segment, start, matcher.start());
          foundParam = true;
          String group = matcher.group();
          // Regular expressions can have '{' and '}' characters.  Recover earlier replacement
          params.add(PathHelper.recoverEnclosedCurlyBraces(group));
-         matcher.appendReplacement(newSegment, "_resteasy_uri_parameter");
+         newSegment.append("_resteasy_uri_parameter");
+         start = matcher.end();
       }
-      matcher.appendTail(newSegment);
+      newSegment.append(segment, start, segment.length());
       return foundParam;
    }
 
@@ -309,11 +308,11 @@ public class Encode
     * @param encoding
     * @return
     */
-   private static String encodeValue(String segment, String[] encoding)
+   public static String encodeValue(String segment, String[] encoding)
    {
       ArrayList<String> params = new ArrayList<String>();
       boolean foundParam = false;
-      StringBuffer newSegment = new StringBuffer();
+      StringBuilder newSegment = new StringBuilder();
       if (savePathParams(segment, newSegment, params))
       {
          foundParam = true;
@@ -411,21 +410,21 @@ public class Encode
       return encodeFromArray(nameOrValue, queryNameValueEncoding, true);
    }
 
-   private static String encodeFromArray(String segment, String[] encodingMap, boolean encodePercent)
+   protected static String encodeFromArray(String segment, String[] encodingMap, boolean encodePercent)
    {
-      StringBuffer result = new StringBuffer();
+      StringBuilder result = new StringBuilder();
       for (int i = 0; i < segment.length(); i++)
       {
-         if (!encodePercent && segment.charAt(i) == '%')
+    	 char currentChar = segment.charAt(i);
+         if (!encodePercent && currentChar == '%')
          {
-            result.append(segment.charAt(i));
+            result.append(currentChar);
             continue;
          }
-         int idx = segment.charAt(i);
-         String encoding = encode(idx, encodingMap);
+         String encoding = encode(currentChar, encodingMap);
          if (encoding == null)
          {
-            result.append(segment.charAt(i));
+            result.append(currentChar);
          }
          else
          {
@@ -461,20 +460,20 @@ public class Encode
       return encoded;
    }
 
-   private static String pathParamReplacement(String segment, List<String> params)
+   public static String pathParamReplacement(String segment, List<String> params)
    {
-      StringBuffer newSegment = new StringBuffer();
+      StringBuilder newSegment = new StringBuilder();
       Matcher matcher = PARAM_REPLACEMENT.matcher(segment);
       int i = 0;
+      int start = 0;
       while (matcher.find())
       {
+    	 newSegment.append(segment, start, matcher.start());
          String replacement = params.get(i++);
-         // double encode slashes, so that slashes stay where they are 
-         replacement = replacement.replace("\\", "\\\\");
-         replacement = replacement.replace("$", "\\$");
-         matcher.appendReplacement(newSegment, replacement);
+     	 newSegment.append(replacement);
+		 start = matcher.end();
       }
-      matcher.appendTail(newSegment);
+  	  newSegment.append(segment, start, segment.length());
       segment = newSegment.toString();
       return segment;
    }
@@ -505,6 +504,38 @@ public class Encode
       }
       return decoded;
    }
+   
+   /**
+    * decode an encoded map
+    *
+    * @param map
+    * @param charset
+    * @return
+    */
+   public static MultivaluedHashMap<String, String> decode(MultivaluedHashMap<String, String> map, String charset)
+   {
+      if (charset == null)
+      {
+         charset = UTF_8;
+      }
+      MultivaluedHashMap<String, String> decoded = new MultivaluedHashMap<String, String>();
+      for (Map.Entry<String, List<String>> entry : map.entrySet())
+      {
+         List<String> values = entry.getValue();
+         for (String value : values)
+         {
+            try
+            {
+               decoded.add(URLDecoder.decode(entry.getKey(), charset), URLDecoder.decode(value, charset));
+            }
+            catch (UnsupportedEncodingException e)
+            {
+               throw new RuntimeException(e);
+            }
+         }
+      }
+      return decoded;
+   }
 
    public static MultivaluedHashMap<String, String> encode(MultivaluedHashMap<String, String> map)
    {
diff --git a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
index f064163..a03c53c 100755
--- a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
+++ b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
@@ -614,7 +614,7 @@ public class KeycloakUriBuilder {
             if (value == null) throw new IllegalArgumentException("A passed in value was null");
             if (query == null) query = "";
             else query += "&";
-            query += Encode.encodeQueryParam(name) + "=" + Encode.encodeQueryParam(value.toString());
+            query += Encode.encodeQueryParamAsIs(name) + "=" + Encode.encodeQueryParamAsIs(value.toString());
         }
         return this;
     }
diff --git a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index 86b3ecb..be74b74 100755
--- a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -345,7 +345,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
         logger.debugv("saml document: {0}", documentAsString);
         byte[] responseBytes = documentAsString.getBytes(GeneralConstants.SAML_CHARSET);
 
-        return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
+        return RedirectBindingUtil.deflateBase64Encode(responseBytes);
     }
 
 
@@ -370,7 +370,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
             } catch (InvalidKeyException | SignatureException e) {
                 throw new ProcessingException(e);
             }
-            String encodedSig = RedirectBindingUtil.base64URLEncode(sig);
+            String encodedSig = RedirectBindingUtil.base64Encode(sig);
             builder.queryParam(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY, encodedSig);
         }
         return builder.build();
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/web/util/RedirectBindingUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/web/util/RedirectBindingUtil.java
index 587113c..9c0938f 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/web/util/RedirectBindingUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/web/util/RedirectBindingUtil.java
@@ -61,6 +61,19 @@ public class RedirectBindingUtil {
     }
 
     /**
+     * On the byte array, apply base64 encoding
+     *
+     * @param stringToEncode
+     *
+     * @return
+     *
+     * @throws IOException
+     */
+    public static String base64Encode(byte[] stringToEncode) throws IOException {
+        return Base64.encodeBytes(stringToEncode, Base64.DONT_BREAK_LINES);
+    }
+
+    /**
      * On the byte array, apply base64 encoding following by URL encoding
      *
      * @param stringToEncode
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
index f2fdd0c..9027ed5 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
@@ -91,7 +91,7 @@ public abstract class OIDCRedirectUriBuilder {
 
         @Override
         public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
-            String param = paramName + "=" + Encode.encodeQueryParam(paramValue);
+            String param = paramName + "=" + Encode.encodeQueryParamAsIs(paramValue);
             if (fragment == null) {
                 fragment = new StringBuilder(param);
             } else {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriStateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriStateTest.java
new file mode 100644
index 0000000..db410d5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriStateTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.oauth;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.util.OAuthClient;
+
+public class OAuthRedirectUriStateTest extends AbstractTestRealmKeycloakTest {
+
+    @Override
+    public void configureTestRealm(RealmRepresentation testRealm) {
+    }
+
+    @Before
+    public void clientConfiguration() {
+        oauth.clientId("test-app");
+        oauth.responseType(OIDCResponseType.CODE);
+        oauth.stateParamRandom();
+    }
+
+    void assertStateReflected(String state) {
+        oauth.stateParamHardcoded(state);
+
+        OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+        Assert.assertNotNull(response.getCode());
+
+        URL url;
+        try {
+            url = new URL(driver.getCurrentUrl());
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
+        Assert.assertTrue(url.getQuery().contains("state=" + state));
+    }
+
+    @Test
+    public void testSimpleStateParameter() {
+        assertStateReflected("VeryLittleGravitasIndeed");
+    }
+
+    @Test
+    public void testJsonStateParameter() {
+        assertStateReflected("%7B%22csrf_token%22%3A%2B%22hlvZNIsWyqdkEhbjlQIia0ty2YY4TXat%22%2C%2B%22destination%22%3A%2B%22eyJhbGciOiJIUzI1NiJ9.Imh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9wcml2YXRlIg.T18WeIV29komDl8jav-3bSnUZDlMD8VOfIrd2ikP5zE%22%7D");
+    }
+}