keycloak-uncached

Details

diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index c576a5d..e49842b 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -289,10 +289,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
             }
             JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
 
-            String aud = token.getAudience();
             String iss = token.getIssuer();
 
-            if (aud != null && !aud.equals(getConfig().getClientId())) {
+            if (!token.hasAudience(getConfig().getClientId())) {
                 throw new IdentityBrokerException("Wrong audience from token.");
             }
 
diff --git a/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java b/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java
new file mode 100644
index 0000000..00b8173
--- /dev/null
+++ b/core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java
@@ -0,0 +1,30 @@
+package org.keycloak.json;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.DeserializationContext;
+import org.codehaus.jackson.map.JsonDeserializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class StringOrArrayDeserializer extends JsonDeserializer<Object> {
+
+    @Override
+    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+        JsonNode jsonNode = jsonParser.readValueAsTree();
+        if (jsonNode.isArray()) {
+            ArrayList<String> a = new ArrayList<>(1);
+            Iterator<JsonNode> itr = jsonNode.iterator();
+            while (itr.hasNext()) {
+                a.add(itr.next().getTextValue());
+            }
+            return a.toArray(new String[a.size()]);
+        } else {
+            return new String[] { jsonNode.getTextValue() };
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java
new file mode 100644
index 0000000..f9b3547
--- /dev/null
+++ b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java
@@ -0,0 +1,25 @@
+package org.keycloak.json;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+
+import java.io.IOException;
+
+public class StringOrArraySerializer extends JsonSerializer<Object> {
+    @Override
+    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+        String[] array = (String[]) o;
+        if (array == null) {
+            jsonGenerator.writeNull();
+        } else if (array.length == 1) {
+            jsonGenerator.writeString(array[0]);
+        } else {
+            jsonGenerator.writeStartArray();
+            for (String s : array) {
+                jsonGenerator.writeString(s);
+            }
+            jsonGenerator.writeEndArray();
+        }
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 1380c2f..092298e 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -164,11 +164,6 @@ public class AccessToken extends IDToken {
     }
 
     @Override
-    public AccessToken audience(String audience) {
-        return (AccessToken) super.audience(audience);
-    }
-
-    @Override
     public AccessToken subject(String subject) {
         return (AccessToken) super.subject(subject);
     }
diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java
index 42f7679..499180b 100755
--- a/core/src/main/java/org/keycloak/representations/IDToken.java
+++ b/core/src/main/java/org/keycloak/representations/IDToken.java
@@ -1,12 +1,6 @@
 package org.keycloak.representations;
 
-import org.codehaus.jackson.annotate.JsonAnyGetter;
-import org.codehaus.jackson.annotate.JsonAnySetter;
 import org.codehaus.jackson.annotate.JsonProperty;
-import org.codehaus.jackson.annotate.JsonUnwrapped;
-
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
index f714d4f..b547fca 100755
--- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java
+++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java
@@ -4,6 +4,10 @@ import org.codehaus.jackson.annotate.JsonAnyGetter;
 import org.codehaus.jackson.annotate.JsonAnySetter;
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonDeserialize;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.keycloak.json.StringOrArrayDeserializer;
+import org.keycloak.json.StringOrArraySerializer;
 import org.keycloak.util.Time;
 
 import java.io.Serializable;
@@ -26,14 +30,16 @@ public class JsonWebToken implements Serializable {
     @JsonProperty("iss")
     protected String issuer;
     @JsonProperty("aud")
-    protected String audience;
+    @JsonSerialize(using = StringOrArraySerializer.class)
+    @JsonDeserialize(using = StringOrArrayDeserializer.class)
+    protected String[] audience;
     @JsonProperty("sub")
     protected String subject;
     @JsonProperty("typ")
     protected String type;
     @JsonProperty("azp")
     public String issuedFor;
-    protected Map<String, Object> otherClaims = new HashMap<String, Object>();
+    protected Map<String, Object> otherClaims = new HashMap<>();
 
     public String getId() {
         return id;
@@ -72,7 +78,6 @@ public class JsonWebToken implements Serializable {
     @JsonIgnore
     public boolean isNotBefore() {
         return Time.currentTime() >= notBefore;
-
     }
 
     /**
@@ -113,12 +118,21 @@ public class JsonWebToken implements Serializable {
         return this;
     }
 
-
-    public String getAudience() {
+    @JsonIgnore
+    public String[] getAudience() {
         return audience;
     }
 
-    public JsonWebToken audience(String audience) {
+    public boolean hasAudience(String audience) {
+        for (String a : this.audience) {
+            if (a.equals(audience)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public JsonWebToken audience(String... audience) {
         this.audience = audience;
         return this;
     }
diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java
index ff080de..a1a93ba 100755
--- a/core/src/main/java/org/keycloak/util/JsonSerialization.java
+++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java
@@ -3,7 +3,6 @@ package org.keycloak.util;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
 import org.codehaus.jackson.map.annotate.JsonSerialize;
-import org.codehaus.jackson.type.TypeReference;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java
new file mode 100644
index 0000000..dbe0ecb
--- /dev/null
+++ b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java
@@ -0,0 +1,44 @@
+package org.keycloak.jose;
+
+import org.junit.Test;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+/**
+ * Created by st on 20.08.15.
+ */
+public class JsonWebTokenTest {
+
+    @Test
+    public void testAudSingle() throws IOException {
+        String single = "{ \"aud\": \"test\" }";
+        JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class);
+        assertArrayEquals(new String[] { "test" }, s.getAudience());
+    }
+
+    @Test
+    public void testAudArray() throws IOException {
+        String single = "{ \"aud\": [\"test\"] }";
+        JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class);
+        assertArrayEquals(new String[]{"test"}, s.getAudience());
+    }
+
+    @Test
+    public void test() throws IOException {
+        JsonWebToken jsonWebToken = new JsonWebToken();
+        jsonWebToken.audience("test");
+        assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : \"test\""));
+    }
+
+    @Test
+    public void testArray() throws IOException {
+        JsonWebToken jsonWebToken = new JsonWebToken();
+        jsonWebToken.audience("test", "test2");
+        assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : [ \"test\", \"test2\" ]"));
+    }
+
+}
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 8917581..46d3b18 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -309,7 +309,7 @@
                                     var tokenResponse = JSON.parse(req.responseText);
                                     setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
 
-                                    kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat;
+                                    kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
 
                                     kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
                                     for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
@@ -402,7 +402,7 @@
                             var tokenResponse = JSON.parse(req.responseText);
                             setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
 
-                            kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat;
+                            kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
 
                             kc.onAuthSuccess && kc.onAuthSuccess();
                             promise && promise.setSuccess();
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 5191ed0..3a98c8d 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -111,13 +111,9 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
             }
 
             // Validate other things
-            String audience = token.getAudience();
             String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName());
-            if (audience == null) {
-                throw new RuntimeException("Audience is null on JWT");
-            }
-            if (!audience.equals(expectedAudience)) {
-                throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + audience + "'");
+            if (!token.hasAudience(expectedAudience)) {
+                throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + token.getAudience() + "'");
             }
 
             if (!token.isActive()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index c5a7f8f..15c5f38 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -190,8 +190,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
 
             if (authResult != null) {
                 AccessToken token = authResult.getToken();
-                String audience = token.getAudience();
-                ClientModel clientModel = this.realmModel.getClientByClientId(audience);
+                String[] audience = token.getAudience();
+                ClientModel clientModel = this.realmModel.getClientByClientId(audience[0]);
 
                 if (clientModel == null) {
                     return badRequest("Invalid client.");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index 8ce5c7c..fb2b5df 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -48,6 +48,7 @@ import org.keycloak.testsuite.pages.VerifyEmailPage;
 import org.keycloak.testsuite.rule.GreenMailRule;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.util.JsonSerialization;
 import org.openqa.selenium.By;
 import org.openqa.selenium.NoSuchElementException;
 import org.openqa.selenium.WebDriver;
@@ -801,10 +802,9 @@ public abstract class AbstractIdentityProviderTest {
         UserSessionStatus sessionStatus = null;
 
         try {
-            ObjectMapper objectMapper = new ObjectMapper();
             String pageSource = this.driver.getPageSource();
 
-            sessionStatus = objectMapper.readValue(pageSource.getBytes(), UserSessionStatus.class);
+            sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class);
         } catch (IOException ignore) {
             ignore.printStackTrace();
         }