From 5ca3a48094fdbcc60e07ec3919e5753e8a32b82b Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 14:54:46 +0200 Subject: [PATCH] KEYCLOAK-1723 Allow aud to be single field or array --- .../broker/oidc/OIDCIdentityProvider.java | 3 +- .../json/StringOrArrayDeserializer.java | 30 + .../json/StringOrArraySerializer.java | 25 + .../keycloak/representations/AccessToken.java | 5 - .../org/keycloak/representations/IDToken.java | 6 - .../representations/JsonWebToken.java | 26 +- .../org/keycloak/util/JsonSerialization.java | 1 - .../org/keycloak/jose/JsonWebTokenTest.java | 44 + .../js-console/src/main/webapp/keycloak.js | 886 ------------------ integration/js/src/main/resources/keycloak.js | 4 +- .../client/JWTClientAuthenticator.java | 8 +- .../resources/IdentityBrokerService.java | 4 +- .../broker/AbstractIdentityProviderTest.java | 4 +- 13 files changed, 128 insertions(+), 918 deletions(-) create mode 100644 core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java create mode 100644 core/src/main/java/org/keycloak/json/StringOrArraySerializer.java create mode 100644 core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java delete mode 100755 examples/js-console/src/main/webapp/keycloak.js 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 c576a5d6cd..e49842bc51 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 { + + @Override + public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode jsonNode = jsonParser.readValueAsTree(); + if (jsonNode.isArray()) { + ArrayList a = new ArrayList<>(1); + Iterator 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 0000000000..f9b354766b --- /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 { + @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 1380c2febe..092298e848 100755 --- a/core/src/main/java/org/keycloak/representations/AccessToken.java +++ b/core/src/main/java/org/keycloak/representations/AccessToken.java @@ -163,11 +163,6 @@ public class AccessToken extends IDToken { return (AccessToken) super.issuer(issuer); } - @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 42f767959e..499180bb2f 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 Bill Burke diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java index f714d4ff69..b547fcabe4 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 otherClaims = new HashMap(); + protected Map 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 ff080def36..a1a93ba1cc 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 0000000000..dbe0ecb690 --- /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/examples/js-console/src/main/webapp/keycloak.js b/examples/js-console/src/main/webapp/keycloak.js deleted file mode 100755 index 8917581af4..0000000000 --- a/examples/js-console/src/main/webapp/keycloak.js +++ /dev/null @@ -1,886 +0,0 @@ -(function( window, undefined ) { - - var Keycloak = function (config) { - if (!(this instanceof Keycloak)) { - return new Keycloak(config); - } - - var kc = this; - var adapter; - var refreshQueue = []; - - var loginIframe = { - enable: true, - callbackMap: [], - interval: 5 - }; - - kc.init = function (initOptions) { - kc.authenticated = false; - - if (window.Cordova) { - adapter = loadAdapter('cordova'); - } else { - adapter = loadAdapter(); - } - - if (initOptions) { - if (typeof initOptions.checkLoginIframe !== 'undefined') { - loginIframe.enable = initOptions.checkLoginIframe; - } - - if (initOptions.checkLoginIframeInterval) { - loginIframe.interval = initOptions.checkLoginIframeInterval; - } - - if (initOptions.onLoad === 'login-required') { - kc.loginRequired = true; - } - } - - var promise = createPromise(); - - var initPromise = createPromise(); - initPromise.promise.success(function() { - kc.onReady && kc.onReady(kc.authenticated); - promise.setSuccess(kc.authenticated); - }).error(function() { - promise.setError(); - }); - - var configPromise = loadConfig(config); - - function onLoad() { - var doLogin = function(prompt) { - if (!prompt) { - options.prompt = 'none'; - } - kc.login(options).success(function () { - initPromise.setSuccess(); - }).error(function () { - initPromise.setError(); - }); - } - - var options = {}; - switch (initOptions.onLoad) { - case 'check-sso': - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe().success(function () { - doLogin(false); - }).error(function () { - initPromise.setSuccess(); - }); - }); - } else { - doLogin(false); - } - break; - case 'login-required': - doLogin(true); - break; - default: - throw 'Invalid value for onLoad'; - } - } - - function processInit() { - var callback = parseCallback(window.location.href); - - if (callback) { - setupCheckLoginIframe(); - window.history.replaceState({}, null, callback.newUrl); - processCallback(callback, initPromise); - return; - } else if (initOptions) { - if (initOptions.token || initOptions.refreshToken) { - setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); - - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe().success(function () { - initPromise.setSuccess(); - }).error(function () { - if (initOptions.onLoad) { - onLoad(); - } - }); - }); - } else { - initPromise.setSuccess(); - } - } else if (initOptions.onLoad) { - onLoad(); - } - } else { - initPromise.setSuccess(); - } - } - - configPromise.success(processInit); - configPromise.error(function() { - promise.setError(); - }); - - return promise.promise; - } - - kc.login = function (options) { - return adapter.login(options); - } - - kc.createLoginUrl = function(options) { - var state = createUUID(); - - var redirectUri = adapter.redirectUri(options); - if (options && options.prompt) { - redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt; - } - - sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) }); - - var action = 'auth'; - if (options && options.action == 'register') { - action = 'registrations'; - } - - var url = getRealmUrl() - + '/protocol/openid-connect/' + action - + '?client_id=' + encodeURIComponent(kc.clientId) - + '&redirect_uri=' + encodeURIComponent(redirectUri) - + '&state=' + encodeURIComponent(state) - + '&response_type=code'; - - if (options && options.prompt) { - url += '&prompt=' + options.prompt; - } - - if (options && options.loginHint) { - url += '&login_hint=' + options.loginHint; - } - - if (options && options.idpHint) { - url += '&kc_idp_hint=' + options.idpHint; - } - - return url; - } - - kc.logout = function(options) { - return adapter.logout(options); - } - - kc.createLogoutUrl = function(options) { - var url = getRealmUrl() - + '/protocol/openid-connect/logout' - + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options)); - - return url; - } - - kc.createAccountUrl = function(options) { - var url = getRealmUrl() - + '/account' - + '?referrer=' + encodeURIComponent(kc.clientId) - + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); - - return url; - } - - kc.accountManagement = function() { - return adapter.accountManagement(); - } - - kc.hasRealmRole = function (role) { - var access = kc.realmAccess; - return !!access && access.roles.indexOf(role) >= 0; - } - - kc.hasResourceRole = function(role, resource) { - if (!kc.resourceAccess) { - return false; - } - - var access = kc.resourceAccess[resource || kc.clientId]; - return !!access && access.roles.indexOf(role) >= 0; - } - - kc.loadUserProfile = function() { - var url = getRealmUrl() + '/account'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - kc.profile = JSON.parse(req.responseText); - promise.setSuccess(kc.profile); - } else { - promise.setError(); - } - } - } - - req.send(); - - return promise.promise; - } - - kc.loadUserInfo = function() { - var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - kc.userInfo = JSON.parse(req.responseText); - promise.setSuccess(kc.userInfo); - } else { - promise.setError(); - } - } - } - - req.send(); - - return promise.promise; - } - - kc.isTokenExpired = function(minValidity) { - if (!kc.tokenParsed || !kc.refreshToken) { - throw 'Not authenticated'; - } - - var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew; - if (minValidity) { - expiresIn -= minValidity; - } - - return expiresIn < 0; - } - - kc.updateToken = function(minValidity) { - var promise = createPromise(); - - if (!kc.tokenParsed || !kc.refreshToken) { - promise.setError(); - return promise.promise; - } - - minValidity = minValidity || 5; - - var exec = function() { - if (!kc.isTokenExpired(minValidity)) { - promise.setSuccess(false); - } else { - var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - refreshQueue.push(promise); - - if (refreshQueue.length == 1) { - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - var timeLocal = new Date().getTime(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - timeLocal = (timeLocal + new Date().getTime()) / 2; - - 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.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setSuccess(true); - } - } else { - kc.onAuthRefreshError && kc.onAuthRefreshError(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setError(true); - } - } - } - }; - - req.send(params); - } - } - } - - if (loginIframe.enable) { - var iframePromise = checkLoginIframe(); - iframePromise.success(function() { - exec(); - }).error(function() { - promise.setError(); - }); - } else { - exec(); - } - - return promise.promise; - } - - kc.clearToken = function() { - if (kc.token) { - setToken(null, null, null); - kc.onAuthLogout && kc.onAuthLogout(); - if (kc.loginRequired) { - kc.login(); - } - } - } - - function getRealmUrl() { - if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { - return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); - } else { - return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); - } - } - - function getOrigin() { - if (!window.location.origin) { - return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); - } else { - return window.location.origin; - } - } - - function processCallback(oauth, promise) { - var code = oauth.code; - var error = oauth.error; - var prompt = oauth.prompt; - - if (code) { - var params = 'code=' + code + '&grant_type=authorization_code'; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - params += '&redirect_uri=' + oauth.redirectUri; - - req.withCredentials = true; - - var timeLocal = new Date().getTime(); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - timeLocal = (timeLocal + new Date().getTime()) / 2; - - 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.onAuthSuccess && kc.onAuthSuccess(); - promise && promise.setSuccess(); - } else { - kc.onAuthError && kc.onAuthError(); - promise && promise.setError(); - } - } - }; - - req.send(params); - } else if (error) { - if (prompt != 'none') { - kc.onAuthError && kc.onAuthError(); - promise && promise.setError(); - } else { - promise && promise.setSuccess(); - } - } - } - - function loadConfig(url) { - var promise = createPromise(); - var configUrl; - - if (!config) { - configUrl = 'keycloak.json'; - } else if (typeof config === 'string') { - configUrl = config; - } - - if (configUrl) { - var req = new XMLHttpRequest(); - req.open('GET', configUrl, true); - req.setRequestHeader('Accept', 'application/json'); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - var config = JSON.parse(req.responseText); - - kc.authServerUrl = config['auth-server-url']; - kc.realm = config['realm']; - kc.clientId = config['resource']; - kc.clientSecret = (config['credentials'] || {})['secret']; - - promise.setSuccess(); - } else { - promise.setError(); - } - } - }; - - req.send(); - } else { - if (!config['url']) { - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.match(/.*keycloak\.js/)) { - config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); - break; - } - } - } - - if (!config.realm) { - throw 'realm missing'; - } - - if (!config.clientId) { - throw 'clientId missing'; - } - - kc.authServerUrl = config.url; - kc.realm = config.realm; - kc.clientId = config.clientId; - kc.clientSecret = (config.credentials || {}).secret; - - promise.setSuccess(); - } - - return promise.promise; - } - - function setToken(token, refreshToken, idToken) { - if (token) { - kc.token = token; - kc.tokenParsed = decodeToken(token); - var sessionId = kc.realm + '/' + kc.tokenParsed.sub; - if (kc.tokenParsed.session_state) { - sessionId = sessionId + '/' + kc.tokenParsed.session_state; - } - kc.sessionId = sessionId; - kc.authenticated = true; - kc.subject = kc.tokenParsed.sub; - kc.realmAccess = kc.tokenParsed.realm_access; - kc.resourceAccess = kc.tokenParsed.resource_access; - } else { - delete kc.token; - delete kc.tokenParsed; - delete kc.subject; - delete kc.realmAccess; - delete kc.resourceAccess; - - kc.authenticated = false; - } - - if (refreshToken) { - kc.refreshToken = refreshToken; - kc.refreshTokenParsed = decodeToken(refreshToken); - } else { - delete kc.refreshToken; - delete kc.refreshTokenParsed; - } - - if (idToken) { - kc.idToken = idToken; - kc.idTokenParsed = decodeToken(idToken); - } else { - delete kc.idToken; - delete kc.idTokenParsed; - } - } - - function decodeToken(str) { - str = str.split('.')[1]; - - str = str.replace('/-/g', '+'); - str = str.replace('/_/g', '/'); - switch (str.length % 4) - { - case 0: - break; - case 2: - str += '=='; - break; - case 3: - str += '='; - break; - default: - throw 'Invalid token'; - } - - str = (str + '===').slice(0, str.length + (str.length % 4)); - str = str.replace(/-/g, '+').replace(/_/g, '/'); - - str = decodeURIComponent(escape(atob(str))); - - str = JSON.parse(str); - return str; - } - - function createUUID() { - var s = []; - var hexDigits = '0123456789abcdef'; - for (var i = 0; i < 36; i++) { - s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); - } - s[14] = '4'; - s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = '-'; - var uuid = s.join(''); - return uuid; - } - - kc.callback_id = 0; - - function createCallbackId() { - var id = ''; - return id; - - } - - function parseCallback(url) { - if (url.indexOf('?') != -1) { - var oauth = {}; - - oauth.newUrl = url.split('?')[0]; - var paramString = url.split('?')[1]; - var fragIndex = paramString.indexOf('#'); - if (fragIndex != -1) { - paramString = paramString.substring(0, fragIndex); - } - var params = paramString.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - switch (decodeURIComponent(p[0])) { - case 'code': - oauth.code = p[1]; - break; - case 'error': - oauth.error = p[1]; - break; - case 'state': - oauth.state = decodeURIComponent(p[1]); - break; - case 'redirect_fragment': - oauth.fragment = decodeURIComponent(p[1]); - break; - case 'prompt': - oauth.prompt = p[1]; - break; - default: - oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1]; - break; - } - } - - var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState); - - if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) { - delete sessionStorage.oauthState; - - oauth.redirectUri = sessionState.redirectUri; - - if (oauth.fragment) { - oauth.newUrl += '#' + oauth.fragment; - } - - return oauth; - } - } - } - - function createPromise() { - var p = { - setSuccess: function(result) { - p.success = true; - p.result = result; - if (p.successCallback) { - p.successCallback(result); - } - }, - - setError: function(result) { - p.error = true; - p.result = result; - if (p.errorCallback) { - p.errorCallback(result); - } - }, - - promise: { - success: function(callback) { - if (p.success) { - callback(p.result); - } else if (!p.error) { - p.successCallback = callback; - } - return p.promise; - }, - error: function(callback) { - if (p.error) { - callback(p.result); - } else if (!p.success) { - p.errorCallback = callback; - } - return p.promise; - } - } - } - return p; - } - - function setupCheckLoginIframe() { - var promise = createPromise(); - - if (!loginIframe.enable) { - promise.setSuccess(); - return promise.promise; - } - - if (loginIframe.iframe) { - promise.setSuccess(); - return promise.promise; - } - - var iframe = document.createElement('iframe'); - loginIframe.iframe = iframe; - - iframe.onload = function() { - var realmUrl = getRealmUrl(); - if (realmUrl.charAt(0) === '/') { - loginIframe.iframeOrigin = getOrigin(); - } else { - loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); - } - promise.setSuccess(); - - setTimeout(check, loginIframe.interval * 1000); - } - - var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin(); - iframe.setAttribute('src', src ); - iframe.style.display = 'none'; - document.body.appendChild(iframe); - - var messageCallback = function(event) { - if (event.origin !== loginIframe.iframeOrigin) { - return; - } - var data = JSON.parse(event.data); - var promise = loginIframe.callbackMap[data.callbackId]; - delete loginIframe.callbackMap[data.callbackId]; - - if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) { - promise.setSuccess(); - } else { - kc.clearToken(); - promise.setError(); - } - }; - window.addEventListener('message', messageCallback, false); - - var check = function() { - checkLoginIframe(); - if (kc.token) { - setTimeout(check, loginIframe.interval * 1000); - } - }; - - return promise.promise; - } - - function checkLoginIframe() { - var promise = createPromise(); - - if (loginIframe.iframe && loginIframe.iframeOrigin) { - var msg = {}; - msg.callbackId = createCallbackId(); - loginIframe.callbackMap[msg.callbackId] = promise; - var origin = loginIframe.iframeOrigin; - loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin); - } else { - promise.setSuccess(); - } - - return promise.promise; - } - - function loadAdapter(type) { - if (!type || type == 'default') { - return { - login: function(options) { - window.location.href = kc.createLoginUrl(options); - return createPromise().promise; - }, - - logout: function(options) { - window.location.href = kc.createLogoutUrl(options); - return createPromise().promise; - }, - - accountManagement : function() { - window.location.href = kc.createAccountUrl(); - return createPromise().promise; - }, - - redirectUri: function(options) { - if (options && options.redirectUri) { - return options.redirectUri; - } else if (kc.redirectUri) { - return kc.redirectUri; - } else { - var redirectUri = location.href; - if (location.hash) { - redirectUri = redirectUri.substring(0, location.href.indexOf('#')); - redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); - } - return redirectUri; - } - } - }; - } - - if (type == 'cordova') { - loginIframe.enable = false; - - return { - login: function(options) { - var promise = createPromise(); - - var o = 'location=no'; - if (options && options.prompt == 'none') { - o += ',hidden=yes'; - } - - var loginUrl = kc.createLoginUrl(options); - var ref = window.open(loginUrl, '_blank', o); - - var callback; - var error; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - callback = parseCallback(event.url); - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf('http://localhost') != 0) { - error = true; - ref.close(); - } - }); - - ref.addEventListener('exit', function(event) { - if (error || !callback) { - promise.setError(); - } else { - processCallback(callback, promise); - } - }); - - return promise.promise; - }, - - logout: function(options) { - var promise = createPromise(); - - var logoutUrl = kc.createLogoutUrl(options); - var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes'); - - var error; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf('http://localhost') != 0) { - error = true; - ref.close(); - } - }); - - ref.addEventListener('exit', function(event) { - if (error) { - promise.setError(); - } else { - kc.clearToken(); - promise.setSuccess(); - } - }); - - return promise.promise; - }, - - accountManagement : function() { - var accountUrl = kc.createAccountUrl(); - var ref = window.open(accountUrl, '_blank', 'location=no'); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - }, - - redirectUri: function(options) { - return 'http://localhost'; - } - } - } - - throw 'invalid adapter type: ' + type; - } - } - - if ( typeof module === "object" && module && typeof module.exports === "object" ) { - module.exports = Keycloak; - } else { - window.Keycloak = Keycloak; - - if ( typeof define === "function" && define.amd ) { - define( "keycloak", [], function () { return Keycloak; } ); - } - } -})( window ); diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index 8917581af4..46d3b18559 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 5191ed0ec1..3a98c8d33b 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 c5a7f8f0ac..15c5f3800c 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 8ce5c7ce6b..fb2b5dfd4b 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(); }