[KEYCLOAK-16455][Adapter - JavaScript] Propagate 3rd party cookies check

errors outside of JS adapter
This commit is contained in:
Andy Fedotov 2021-04-02 16:16:50 +03:00 committed by Stian Thorgersen
parent ba8d27121c
commit 17b374f53a
5 changed files with 102 additions and 23 deletions

View file

@ -178,6 +178,14 @@ declare namespace Keycloak {
* @default false
*/
enableLogging?: boolean
/**
* Configures how long will Keycloak adapter wait for receiving messages from server in ms. This is used,
* for example, when waiting for response of 3rd party cookies check.
*
* @default 10000
*/
messageReceiveTimeout?: number
}
interface KeycloakLoginOptions {

View file

@ -195,6 +195,12 @@
if (typeof initOptions.scope === 'string') {
kc.scope = initOptions.scope;
}
if (typeof initOptions.messageReceiveTimeout === 'number' && initOptions.messageReceiveTimeout > 0) {
kc.messageReceiveTimeout = initOptions.messageReceiveTimeout;
} else {
kc.messageReceiveTimeout = 10000;
}
}
if (!kc.responseMode) {
@ -211,8 +217,8 @@
initPromise.promise.then(function() {
kc.onReady && kc.onReady(kc.authenticated);
promise.setSuccess(kc.authenticated);
}).catch(function(errorData) {
promise.setError(errorData);
}).catch(function(error) {
promise.setError(error);
});
var configPromise = loadConfig(config);
@ -225,8 +231,8 @@
kc.login(options).then(function () {
initPromise.setSuccess();
}).catch(function () {
initPromise.setError();
}).catch(function (error) {
initPromise.setError(error);
});
}
@ -264,8 +270,8 @@
} else {
initPromise.setSuccess();
}
}).catch(function () {
initPromise.setError();
}).catch(function (error) {
initPromise.setError(error);
});
});
} else {
@ -290,8 +296,8 @@
if (callback && callback.valid) {
return setupCheckLoginIframe().then(function() {
processCallback(callback, initPromise);
}).catch(function (e) {
initPromise.setError();
}).catch(function (error) {
initPromise.setError(error);
});
} else if (initOptions) {
if (initOptions.token && initOptions.refreshToken) {
@ -307,20 +313,20 @@
} else {
initPromise.setSuccess();
}
}).catch(function () {
initPromise.setError();
}).catch(function (error) {
initPromise.setError(error);
});
});
} else {
kc.updateToken(-1).then(function() {
kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess();
}).catch(function() {
}).catch(function(error) {
kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) {
onLoad();
} else {
initPromise.setError();
initPromise.setError(error);
}
});
}
@ -351,13 +357,15 @@
}
configPromise.then(function () {
domReady().then(check3pCookiesSupported).then(processInit)
.catch(function() {
promise.setError();
});
domReady()
.then(check3pCookiesSupported)
.then(processInit)
.catch(function (error) {
promise.setError(error);
});
});
configPromise.catch(function() {
promise.setError();
configPromise.catch(function (error) {
promise.setError(error);
});
return promise.promise;
@ -694,8 +702,8 @@
var iframePromise = checkLoginIframe();
iframePromise.then(function() {
exec();
}).catch(function() {
promise.setError();
}).catch(function(error) {
promise.setError(error);
});
} else {
exec();
@ -1206,6 +1214,19 @@
return p;
}
// Function to extend existing native Promise with timeout
function applyTimeoutToPromise(promise, timeout, errorMessage) {
var timeoutHandle = null;
var timeoutPromise = new Promise(function (resolve, reject) {
timeoutHandle = setTimeout(function () {
reject({ "error": errorMessage || "Promise is not settled within timeout of " + timeout + "ms" });
}, timeout);
});
return Promise.race([promise, timeoutPromise]).finally(function () {
clearTimeout(timeoutHandle);
});
}
function setupCheckLoginIframe() {
var promise = createPromise();
@ -1337,7 +1358,7 @@
promise.setSuccess();
}
return promise.promise;
return applyTimeoutToPromise(promise.promise, kc.messageReceiveTimeout, "Timeout when waiting for 3rd party check iframe message.");
}
function loadAdapter(type) {

View file

@ -183,8 +183,8 @@ public class JavascriptTestExecutor {
String script = "var callback = arguments[arguments.length - 1];" +
" window.keycloak.init(" + arguments + ").then(function (authenticated) {" +
" callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
" }).catch(function () {" +
" callback(\"Init Error\");" +
" }).catch(function (error) {" +
" callback(error);" +
" });";
Object output;

View file

@ -30,8 +30,11 @@ import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.util.List;
import java.util.Map;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST2;
@ -214,4 +217,15 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
public JavascriptStateValidator assertEventsDoesntContain(String text) {
return buildFunction(this::assertEventsWebElementDoesntContain, text);
}
public void assertErrorResponse(String expectedError, WebDriver drv, Object output, WebElement evt) {
Assert.assertNotNull("Empty error response", output);
Assert.assertTrue("Invalid error response type", output instanceof Map);
assertThat((Map<String, String>) output, anyOf(hasEntry("error", expectedError), hasEntry("error_description", expectedError)));
}
public JavascriptStateValidator assertErrorResponse(String expectedError) {
return buildFunction(this::assertErrorResponse, expectedError);
}
}

View file

@ -822,6 +822,42 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
.init(defaultArguments(), this::assertInitNotAuth);
}
// In case of incorrect/unavailable realm provided in KeycloakConfig,
// JavaScript Adapter init() should fail-fast and reject Promise with KeycloakError.
@Test
public void checkInitWithInvalidRealm() {
JSObjectBuilder keycloakConfig = JSObjectBuilder.create()
.add("url", authServerContextRootPage + "/auth")
.add("realm", "invalid-realm-name")
.add("clientId", CLIENT_ID);
JSObjectBuilder initOptions = defaultArguments();
testExecutor
.configure(keycloakConfig)
.init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message."));
}
// In case of unavailable Authorization Server due to network or other kind of problems,
// JavaScript Adapter init() should fail-fast and reject Promise with KeycloakError.
@Test
public void checkInitWithUnavailableAuthServer() {
JSObjectBuilder keycloakConfig = JSObjectBuilder.create()
.add("url", "https://localhost:12345/auth")
.add("realm", REALM_NAME)
.add("clientId", CLIENT_ID);
JSObjectBuilder initOptions = defaultArguments();
testExecutor
.configure(keycloakConfig)
.init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message."));
}
protected void assertAdapterIsLoggedIn(WebDriver driver1, Object output, WebElement events) {
assertTrue(testExecutor.isLoggedIn());
}