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;
|
||||
}
|
||||
|
||||
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 {
|
||||
/**
|
||||
* Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
|
||||
|
@ -217,6 +230,11 @@ export interface KeycloakLoginOptions {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -378,6 +378,15 @@ function Keycloak (config) {
|
|||
}
|
||||
}
|
||||
|
||||
function buildClaimsParameter(requestedAcr){
|
||||
var claims = {
|
||||
id_token: {
|
||||
acr: requestedAcr
|
||||
}
|
||||
}
|
||||
return JSON.stringify(claims);
|
||||
}
|
||||
|
||||
kc.createLoginUrl = function(options) {
|
||||
var state = createUUID();
|
||||
var nonce = createUUID();
|
||||
|
@ -445,6 +454,11 @@ function Keycloak (config) {
|
|||
url += '&ui_locales=' + encodeURIComponent(options.locale);
|
||||
}
|
||||
|
||||
if (options && options.acr) {
|
||||
var claimsParameter = buildClaimsParameter(options.acr);
|
||||
url += '&claims=' + encodeURIComponent(claimsParameter);
|
||||
}
|
||||
|
||||
if (kc.pkceMethod) {
|
||||
var codeVerifier = generateCodeVerifier(96);
|
||||
callbackState.pkceCodeVerifier = codeVerifier;
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
package org.keycloak.testsuite.util.javascript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
|
@ -100,7 +104,7 @@ public class JSObjectBuilder {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
@ -111,11 +115,19 @@ public class JSObjectBuilder {
|
|||
.append(option.getKey())
|
||||
.append(" : ");
|
||||
|
||||
if (!skipQuotes(option.getValue())) argument.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("\"");
|
||||
|
||||
argument.append(option.getValue());
|
||||
argument.append(option.getValue());
|
||||
|
||||
if (!skipQuotes(option.getValue())) argument.append("\"");
|
||||
if (!skipQuotes(option.getValue())) argument.append("\"");
|
||||
}
|
||||
comma = ",";
|
||||
}
|
||||
|
||||
|
@ -124,5 +136,8 @@ public class JSObjectBuilder {
|
|||
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.common.Profile;
|
||||
import org.keycloak.common.util.Retry;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.Details;
|
||||
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.EventRepresentation;
|
||||
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.JavascriptTestExecutor;
|
||||
import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.openqa.selenium.TimeoutException;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebDriverException;
|
||||
import org.openqa.selenium.WebElement;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
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
|
||||
public void testUpdateToken() {
|
||||
XMLHttpRequest request = XMLHttpRequest.create()
|
||||
|
|
Loading…
Reference in a new issue