parent
6bce8b80b9
commit
52712d2c82
4 changed files with 105 additions and 5 deletions
18
adapters/oidc/js/dist/keycloak.d.ts
vendored
18
adapters/oidc/js/dist/keycloak.d.ts
vendored
|
@ -39,6 +39,19 @@ export interface KeycloakConfig {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Acr {
|
||||||
|
/**
|
||||||
|
* Array of values, which will be used inside ID Token `acr` claim sent inside the `claims` parameter to Keycloak server during login.
|
||||||
|
* Values should correspond to the ACR levels defined in the ACR to Loa mapping for realm or client or to the numbers (levels) inside defined
|
||||||
|
* Keycloak authentication flow. See section 5.5.1 of OIDC 1.0 specification for the details.
|
||||||
|
*/
|
||||||
|
values: string[];
|
||||||
|
/**
|
||||||
|
* This parameter specifies if ACR claims is considered essential or not.
|
||||||
|
*/
|
||||||
|
essential: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface KeycloakInitOptions {
|
export interface KeycloakInitOptions {
|
||||||
/**
|
/**
|
||||||
* Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
|
* Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
|
||||||
|
@ -217,6 +230,11 @@ export interface KeycloakLoginOptions {
|
||||||
*/
|
*/
|
||||||
loginHint?: string;
|
loginHint?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the `acr` claim of the ID token sent inside the `claims` parameter. See section 5.5.1 of the OIDC 1.0 specification.
|
||||||
|
*/
|
||||||
|
acr?: Acr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -378,6 +378,15 @@ function Keycloak (config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildClaimsParameter(requestedAcr){
|
||||||
|
var claims = {
|
||||||
|
id_token: {
|
||||||
|
acr: requestedAcr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JSON.stringify(claims);
|
||||||
|
}
|
||||||
|
|
||||||
kc.createLoginUrl = function(options) {
|
kc.createLoginUrl = function(options) {
|
||||||
var state = createUUID();
|
var state = createUUID();
|
||||||
var nonce = createUUID();
|
var nonce = createUUID();
|
||||||
|
@ -445,6 +454,11 @@ function Keycloak (config) {
|
||||||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.acr) {
|
||||||
|
var claimsParameter = buildClaimsParameter(options.acr);
|
||||||
|
url += '&claims=' + encodeURIComponent(claimsParameter);
|
||||||
|
}
|
||||||
|
|
||||||
if (kc.pkceMethod) {
|
if (kc.pkceMethod) {
|
||||||
var codeVerifier = generateCodeVerifier(96);
|
var codeVerifier = generateCodeVerifier(96);
|
||||||
callbackState.pkceCodeVerifier = codeVerifier;
|
callbackState.pkceCodeVerifier = codeVerifier;
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package org.keycloak.testsuite.util.javascript;
|
package org.keycloak.testsuite.util.javascript;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author mhajas
|
* @author mhajas
|
||||||
*/
|
*/
|
||||||
|
@ -100,7 +104,7 @@ public class JSObjectBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean skipQuotes(Object o) {
|
private boolean skipQuotes(Object o) {
|
||||||
return (o instanceof Integer || o instanceof Boolean);
|
return (o instanceof Integer || o instanceof Boolean || o instanceof JSObjectBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String build() {
|
public String build() {
|
||||||
|
@ -111,11 +115,19 @@ public class JSObjectBuilder {
|
||||||
.append(option.getKey())
|
.append(option.getKey())
|
||||||
.append(" : ");
|
.append(" : ");
|
||||||
|
|
||||||
|
if (option.getValue().getClass().isArray()) {
|
||||||
|
try {
|
||||||
|
argument.append(JsonSerialization.writeValueAsString(option.getValue()));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new IllegalArgumentException("Not possible to serialize value of the option " + option.getKey(), ioe);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (!skipQuotes(option.getValue())) argument.append("\"");
|
if (!skipQuotes(option.getValue())) argument.append("\"");
|
||||||
|
|
||||||
argument.append(option.getValue());
|
argument.append(option.getValue());
|
||||||
|
|
||||||
if (!skipQuotes(option.getValue())) argument.append("\"");
|
if (!skipQuotes(option.getValue())) argument.append("\"");
|
||||||
|
}
|
||||||
comma = ",";
|
comma = ",";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,5 +136,8 @@ public class JSObjectBuilder {
|
||||||
return argument.toString();
|
return argument.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,12 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.admin.client.resource.ClientResource;
|
import org.keycloak.admin.client.resource.ClientResource;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
import org.keycloak.common.util.Retry;
|
import org.keycloak.common.util.Retry;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
import org.keycloak.representations.ClaimsRepresentation;
|
||||||
|
import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.EventRepresentation;
|
import org.keycloak.representations.idm.EventRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -33,11 +37,14 @@ import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
|
||||||
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
|
import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
|
||||||
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutor;
|
import org.keycloak.testsuite.util.javascript.JavascriptTestExecutor;
|
||||||
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
|
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.openqa.selenium.TimeoutException;
|
import org.openqa.selenium.TimeoutException;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
import org.openqa.selenium.WebDriverException;
|
import org.openqa.selenium.WebDriverException;
|
||||||
import org.openqa.selenium.WebElement;
|
import org.openqa.selenium.WebElement;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -498,6 +505,52 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for acr handling via {@code loginOptions}: <pre>{@code
|
||||||
|
* Keycloak keycloak = new Keycloak(); keycloak.login({.... acr: { values: ["foo", "bar"], essential: false}})
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAcrInLoginOptionsShouldBeConsideredByLoginUrl() {
|
||||||
|
// Test when no "acr" option given. Claims parameter won't be passed to Keycloak server
|
||||||
|
testExecutor.configure().init(defaultArguments());
|
||||||
|
JSObjectBuilder loginOptions = JSObjectBuilder.create();
|
||||||
|
|
||||||
|
testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> {
|
||||||
|
try {
|
||||||
|
String queryString = new URL(driver.getCurrentUrl()).getQuery();
|
||||||
|
String claimsParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
Assert.assertNull(claimsParam);
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new AssertionError(ioe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test given "acr" option will be translated into the "claims" parameter passed to Keycloak server
|
||||||
|
jsDriver.navigate().to(testAppUrl);
|
||||||
|
testExecutor.configure().init(defaultArguments());
|
||||||
|
|
||||||
|
JSObjectBuilder acr1 = JSObjectBuilder.create()
|
||||||
|
.add("values", new String[] {"foo", "bar"})
|
||||||
|
.add("essential", false);
|
||||||
|
loginOptions = JSObjectBuilder.create().add("acr", acr1);
|
||||||
|
|
||||||
|
testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> {
|
||||||
|
try {
|
||||||
|
String queryString = new URL(driver.getCurrentUrl()).getQuery();
|
||||||
|
String claimsParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.CLAIMS_PARAM);
|
||||||
|
Assert.assertNotNull(claimsParam);
|
||||||
|
|
||||||
|
ClaimsRepresentation claimsRep = JsonSerialization.readValue(claimsParam, ClaimsRepresentation.class);
|
||||||
|
ClaimsRepresentation.ClaimValue<String> claimValue = claimsRep.getClaimValue(IDToken.ACR, ClaimsRepresentation.ClaimContext.ID_TOKEN, String.class);
|
||||||
|
Assert.assertNames(claimValue.getValues(), "foo", "bar");
|
||||||
|
Assert.assertThat(claimValue.isEssential(), is(false));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new AssertionError(ioe);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateToken() {
|
public void testUpdateToken() {
|
||||||
XMLHttpRequest request = XMLHttpRequest.create()
|
XMLHttpRequest request = XMLHttpRequest.create()
|
||||||
|
|
Loading…
Reference in a new issue