KEYCLOAK-18123 Client Policy - Executor : Enforce Backchannel Authentication Request satisfying high security level
This commit is contained in:
parent
63b737545f
commit
43eb2b7c90
9 changed files with 645 additions and 65 deletions
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.services.clientpolicy.context;
|
||||
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelAuthenticationRequestContext;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest;
|
||||
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyContext;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class SecureCibaSessionEnforceExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SecureCibaSessionEnforceExecutor.class);
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
public SecureCibaSessionEnforceExecutor(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderId() {
|
||||
return SecureCibaSessionEnforceExecutorFactory.PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||
switch (context.getEvent()) {
|
||||
case BACKCHANNEL_AUTHENTICATION_REQUEST:
|
||||
BackchannelAuthenticationRequestContext backchannelAuthenticationRequestContext = (BackchannelAuthenticationRequestContext)context;
|
||||
executeOnBackchannelAuthenticationRequest(backchannelAuthenticationRequestContext.getRequest(),
|
||||
backchannelAuthenticationRequestContext.getRequestParameters());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeOnBackchannelAuthenticationRequest(
|
||||
BackchannelAuthenticationEndpointRequest request,
|
||||
MultivaluedMap<String, String> requestParameters) throws ClientPolicyException {
|
||||
logger.trace("Backchannel Authentication Endpoint - authn request");
|
||||
if (request.getBindingMessage() == null) {
|
||||
logger.trace("Missing parameter: binding_message");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: binding_message");
|
||||
}
|
||||
logger.trace("Passed.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class SecureCibaSessionEnforceExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "secure-ciba-session";
|
||||
|
||||
@Override
|
||||
public ClientPolicyExecutorProvider create(KeycloakSession session) {
|
||||
return new SecureCibaSessionEnforceExecutor(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "To distinguish which authentication belongs to which CIBA flow, it refuses backchannel authentication request which lacks 'binding_message' parameter.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelAuthenticationRequestContext;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequestParser;
|
||||
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyContext;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class SecureCibaSignedAuthenticationRequestExecutor implements ClientPolicyExecutorProvider<SecureCibaSignedAuthenticationRequestExecutor.Configuration> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SecureCibaSignedAuthenticationRequestExecutor.class);
|
||||
|
||||
public static final String INVALID_REQUEST_OBJECT = "invalid_request_object";
|
||||
public static final Integer DEFAULT_AVAILABLE_PERIOD = Integer.valueOf(3600); // (sec) from FAPI-CIBA requirement
|
||||
|
||||
private final KeycloakSession session;
|
||||
private Configuration configuration;
|
||||
|
||||
public SecureCibaSignedAuthenticationRequestExecutor(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupConfiguration(SecureCibaSignedAuthenticationRequestExecutor.Configuration config) {
|
||||
if (config == null) {
|
||||
configuration = new Configuration();
|
||||
configuration.setAvailablePeriod(DEFAULT_AVAILABLE_PERIOD);
|
||||
} else {
|
||||
configuration = config;
|
||||
if (config.getAvailablePeriod() == null) {
|
||||
configuration.setAvailablePeriod(DEFAULT_AVAILABLE_PERIOD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Configuration> getExecutorConfigurationClass() {
|
||||
return Configuration.class;
|
||||
}
|
||||
|
||||
public static class Configuration extends ClientPolicyExecutorConfigurationRepresentation {
|
||||
@JsonProperty("available-period")
|
||||
protected Integer availablePeriod;
|
||||
|
||||
public Integer getAvailablePeriod() {
|
||||
return availablePeriod;
|
||||
}
|
||||
|
||||
public void setAvailablePeriod(Integer availablePeriod) {
|
||||
this.availablePeriod = availablePeriod;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderId() {
|
||||
return SecureCibaSignedAuthenticationRequestExecutorFactory.PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||
switch (context.getEvent()) {
|
||||
case BACKCHANNEL_AUTHENTICATION_REQUEST:
|
||||
BackchannelAuthenticationRequestContext backchannelAuthenticationRequestContext = (BackchannelAuthenticationRequestContext)context;
|
||||
executeOnBackchannelAuthenticationRequest(backchannelAuthenticationRequestContext.getRequest(),
|
||||
backchannelAuthenticationRequestContext.getRequestParameters());
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeOnBackchannelAuthenticationRequest(
|
||||
BackchannelAuthenticationEndpointRequest request,
|
||||
MultivaluedMap<String, String> params) throws ClientPolicyException {
|
||||
logger.trace("Backchannel Authentication Endpoint - authn request");
|
||||
|
||||
if (params == null) {
|
||||
logger.trace("request parameter not exist.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameters");
|
||||
}
|
||||
|
||||
String requestParam = params.getFirst(OIDCLoginProtocol.REQUEST_PARAM);
|
||||
String requestUriParam = params.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM);
|
||||
|
||||
if (requestParam == null && requestUriParam == null) {
|
||||
logger.trace("signed authentication request not exist.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: 'request' or 'request_uri'");
|
||||
}
|
||||
|
||||
JsonNode signedAuthReq = (JsonNode)session.getAttribute(BackchannelAuthenticationEndpointRequestParser.CIBA_SIGNED_AUTHENTICATION_REQUEST);
|
||||
|
||||
// check whether signed authentication request exists
|
||||
if (signedAuthReq == null || signedAuthReq.isEmpty()) {
|
||||
logger.trace("signed authentication request not exist.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: : 'request' or 'request_uri'");
|
||||
}
|
||||
|
||||
// check whether "exp" claim exists
|
||||
if (signedAuthReq.get("exp") == null) {
|
||||
logger.trace("exp claim not incuded.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: exp");
|
||||
}
|
||||
|
||||
// check whether signed authentication request not expired
|
||||
long exp = signedAuthReq.get("exp").asLong();
|
||||
if (Time.currentTime() > exp) { // TODO: Time.currentTime() is int while exp is long...
|
||||
logger.trace("request object expired.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Request Expired");
|
||||
}
|
||||
|
||||
// check whether "nbf" claim exists
|
||||
if (signedAuthReq.get("nbf") == null) {
|
||||
logger.trace("nbf claim not incuded.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter in the signed authentication request: nbf");
|
||||
}
|
||||
|
||||
// check whether signed authentication request not yet being processed
|
||||
long nbf = signedAuthReq.get("nbf").asLong();
|
||||
if (Time.currentTime() < nbf) { // TODO: Time.currentTime() is int while nbf is long...
|
||||
logger.trace("request object not yet being processed.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Request not yet being processed");
|
||||
}
|
||||
|
||||
// check whether signed authentication request's available period is short
|
||||
int availablePeriod = Optional.ofNullable(configuration.getAvailablePeriod()).orElse(DEFAULT_AVAILABLE_PERIOD).intValue();
|
||||
if (exp - nbf > availablePeriod) {
|
||||
logger.trace("signed authentication request's available period is long.");
|
||||
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "signed authentication request's available period is long");
|
||||
}
|
||||
|
||||
logger.trace("Passed.");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
|
||||
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
|
||||
*/
|
||||
public class SecureCibaSignedAuthenticationRequestExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "secure-ciba-signed-authn-req";
|
||||
|
||||
public static final String AVAILABLE_PERIOD = "available-period";
|
||||
|
||||
private static final ProviderConfigProperty AVAILABLE_PERIOD_PROPERTY = new ProviderConfigProperty(
|
||||
AVAILABLE_PERIOD, "Available Period", "The maximum period in seconds for which the 'request' signed authentication request used in CIBA backchannel authentication request is considered valid.",
|
||||
ProviderConfigProperty.STRING_TYPE, "3600");
|
||||
|
||||
@Override
|
||||
public ClientPolicyExecutorProvider create(KeycloakSession session) {
|
||||
return new SecureCibaSignedAuthenticationRequestExecutor(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpText() {
|
||||
return "The executor checks whether the client treats the signed authentication request in its CIBA backchannel authentication request by following Financial-grade API CIBA Security Profile.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return new ArrayList<>(Arrays.asList(AVAILABLE_PERIOD_PROPERTY));
|
||||
}
|
||||
|
||||
}
|
|
@ -34,12 +34,12 @@ import org.keycloak.models.UserModel;
|
|||
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelProvider;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.channel.CIBAAuthenticationRequest;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelAuthenticationRequestContext;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequest;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.endpoints.request.BackchannelAuthenticationEndpointRequestParserProcessor;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.resolvers.CIBALoginUserResolver;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||
import org.keycloak.services.clientpolicy.context.BackchannelAuthenticationRequestContext;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
|
|
|
@ -10,3 +10,5 @@ org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory
|
|||
org.keycloak.services.clientpolicy.executor.ConfidentialClientAcceptExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.ConsentRequiredExecutorFactory
|
||||
org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFactory
|
||||
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSessionEnforceExecutorFactory
|
||||
org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutorFactory
|
|
@ -662,9 +662,13 @@ public class TestingOIDCEndpointsApplicationResource {
|
|||
|
||||
// optional
|
||||
// for testing purpose
|
||||
if (request.getBindingMessage() != null && request.getBindingMessage().equals("GODOWN")) throw new BadRequestException("intentional error : GODOWN");
|
||||
String bindingMessage = request.getBindingMessage();
|
||||
if (bindingMessage != null && bindingMessage.equals("GODOWN")) throw new BadRequestException("intentional error : GODOWN");
|
||||
|
||||
authenticationChannelRequests.put(request.getBindingMessage(), new TestAuthenticationChannelRequest(request, rawBearerToken));
|
||||
// binding_message is optional so that it can be null .
|
||||
// only one CIBA flow without binding_message can be accepted per test method by this test mechanism.
|
||||
if (bindingMessage == null) bindingMessage = ChannelRequestDummyKey;
|
||||
authenticationChannelRequests.put(bindingMessage, new TestAuthenticationChannelRequest(request, rawBearerToken));
|
||||
|
||||
return Response.status(Status.CREATED).build();
|
||||
}
|
||||
|
@ -674,6 +678,9 @@ public class TestingOIDCEndpointsApplicationResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public TestAuthenticationChannelRequest getAuthenticationChannel(@QueryParam("bindingMessage") String bindingMessage) {
|
||||
if (bindingMessage == null) bindingMessage = ChannelRequestDummyKey;
|
||||
return authenticationChannelRequests.get(bindingMessage);
|
||||
}
|
||||
|
||||
private static final String ChannelRequestDummyKey = "channel_request_dummy_key";
|
||||
}
|
||||
|
|
|
@ -32,8 +32,10 @@ import static org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChann
|
|||
import static org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse.Status.SUCCEED;
|
||||
import static org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse.Status.UNAUTHORIZED;
|
||||
import static org.keycloak.testsuite.Assert.assertExpiration;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.QUARKUS;
|
||||
import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
|
||||
import static org.keycloak.testsuite.util.ClientPoliciesUtil.createAnyClientConditionConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
@ -44,20 +46,16 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistration;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
|
@ -71,12 +69,13 @@ import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
|||
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelRequest;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSessionEnforceExecutorFactory;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutor;
|
||||
import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.executor.SecureCibaSignedAuthenticationRequestExecutorFactory;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -84,7 +83,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
|||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||
import org.keycloak.services.clientpolicy.condition.AnyClientConditionFactory;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
|
||||
|
@ -97,8 +96,11 @@ import org.keycloak.testsuite.util.InfinispanTestTimeServiceRule;
|
|||
import org.keycloak.testsuite.util.KeycloakModelUtils;
|
||||
import org.keycloak.testsuite.util.Matchers;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.ServerURLs;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPoliciesBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder;
|
||||
import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder;
|
||||
import org.keycloak.testsuite.util.OAuthClient.AuthenticationRequestAcknowledgement;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
|
@ -110,14 +112,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
*/
|
||||
@EnableFeature(value = Profile.Feature.CIBA, skipRestart = true)
|
||||
@AuthServerContainerExclude({REMOTE, QUARKUS})
|
||||
public class CIBATest extends AbstractTestRealmKeycloakTest {
|
||||
public class CIBATest extends AbstractClientPoliciesTest {
|
||||
|
||||
private final String SECOND_TEST_CLIENT_NAME = "test-second-client";
|
||||
private final String SECOND_TEST_CLIENT_SECRET = "passwort-test-second-client";
|
||||
private static final String ERR_MSG_CLIENT_REG_FAIL = "Failed to send request";
|
||||
|
||||
private ClientRegistration reg;
|
||||
|
||||
@Rule
|
||||
public AssertEvents events = new AssertEvents(this);
|
||||
|
||||
|
@ -125,7 +125,8 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
public InfinispanTestTimeServiceRule ispnTestTimeService = new InfinispanTestTimeServiceRule(this);
|
||||
|
||||
@Override
|
||||
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
||||
|
||||
UserRepresentation user = UserBuilder.create()
|
||||
.username("nutzername-schwarz")
|
||||
|
@ -134,7 +135,7 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
.password("passwort-schwarz")
|
||||
.addRoles("user", "offline_access")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
realm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("nutzername-rot")
|
||||
|
@ -143,7 +144,7 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
.password("passwort-rot")
|
||||
.addRoles("user", "offline_access")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
realm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("nutzername-gelb")
|
||||
|
@ -152,7 +153,7 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
.password("passwort-gelb")
|
||||
.addRoles("user", "offline_access")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
realm.getUsers().add(user);
|
||||
|
||||
user = UserBuilder.create()
|
||||
.username("nutzername-deaktiviert")
|
||||
|
@ -161,24 +162,13 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
.password("passwort-deaktiviert")
|
||||
.addRoles("user", "offline_access")
|
||||
.build();
|
||||
testRealm.getUsers().add(user);
|
||||
realm.getUsers().add(user);
|
||||
|
||||
ClientRepresentation confApp = KeycloakModelUtils.createClient(testRealm, SECOND_TEST_CLIENT_NAME);
|
||||
ClientRepresentation confApp = KeycloakModelUtils.createClient(realm, SECOND_TEST_CLIENT_NAME);
|
||||
confApp.setSecret(SECOND_TEST_CLIENT_SECRET);
|
||||
confApp.setServiceAccountsEnabled(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
// get initial access token for Dynamic Client Registration with authentication
|
||||
reg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", TEST_REALM_NAME).build();
|
||||
ClientInitialAccessPresentation token = adminClient.realm(TEST_REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
|
||||
reg.auth(Auth.token(token));
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
reg.close();
|
||||
testRealms.add(realm);
|
||||
}
|
||||
|
||||
private String cibaBackchannelTokenDeliveryMode;
|
||||
|
@ -682,6 +672,16 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
testBackchannelAuthenticationFlow(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackchannelAuthenticationFlowWithoutBindingMessage() throws Exception {
|
||||
testBackchannelAuthenticationFlow(false, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackchannelAuthenticationFlowOfflineAccessWithoutBindingMessage() throws Exception {
|
||||
testBackchannelAuthenticationFlow(true, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleUsersBackchannelAuthenticationFlows() throws Exception {
|
||||
ClientResource clientResource = null;
|
||||
|
@ -1132,13 +1132,10 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
@Test
|
||||
public void testCibaGrantSettingByDynamicClientRegistration() throws Exception {
|
||||
String clientId = createClientDynamically("valid-CIBA-CD", (OIDCClientRepresentation clientRep) -> {
|
||||
});
|
||||
|
||||
String clientId = createClientDynamically(generateSuffixedName("valid-CIBA-CD"), (OIDCClientRepresentation clientRep) -> {});
|
||||
OIDCClientRepresentation rep = getClientDynamically(clientId);
|
||||
Assert.assertFalse(rep.getGrantTypes().contains(OAuth2Constants.CIBA_GRANT_TYPE));
|
||||
Assert.assertNull(rep.getBackchannelAuthenticationRequestSigningAlg());
|
||||
|
||||
updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
|
||||
List<String> grantTypes = Optional.ofNullable(clientRep.getGrantTypes()).orElse(new ArrayList<>());
|
||||
grantTypes.add(OAuth2Constants.CIBA_GRANT_TYPE);
|
||||
|
@ -1171,6 +1168,215 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
testBackchannelAuthenticationFlowWithInvalidSignedAuthenticationRequest(true, "none", 400, "None signed algorithm is not allowed");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureCibaSessionEnforceExecutor() throws Exception {
|
||||
String clientId = createClientDynamically(generateSuffixedName("valid-CIBA-CD"), (OIDCClientRepresentation clientRep) -> {
|
||||
List<String> grantTypes = Optional.ofNullable(clientRep.getGrantTypes()).orElse(new ArrayList<>());
|
||||
grantTypes.add(OAuth2Constants.CIBA_GRANT_TYPE);
|
||||
clientRep.setGrantTypes(grantTypes);
|
||||
});
|
||||
OIDCClientRepresentation rep = getClientDynamically(clientId);
|
||||
String clientSecret = rep.getClientSecret();
|
||||
|
||||
String username = "nutzername-rot";
|
||||
Map<String, String> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put("user_device", "mobile");
|
||||
|
||||
// register profiles
|
||||
String json = (new ClientProfilesBuilder()).addProfile(
|
||||
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
|
||||
.addExecutor(SecureCibaSessionEnforceExecutorFactory.PROVIDER_ID, null)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updateProfiles(json);
|
||||
|
||||
// register policies
|
||||
json = (new ClientPoliciesBuilder()).addPolicy(
|
||||
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
|
||||
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
|
||||
createAnyClientConditionConfig())
|
||||
.addProfile(PROFILE_NAME)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updatePolicies(json);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
AuthenticationRequestAcknowledgement response = oauth.doBackchannelAuthenticationRequest(clientId, clientSecret, username, null, null, additionalParameters);
|
||||
assertThat(response.getStatusCode(), is(equalTo(400)));
|
||||
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
|
||||
assertThat(response.getErrorDescription(), is("Missing parameter: binding_message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureCibaSessionEnforceExecutorWithSignedAuthenticationRequestParam() throws Exception {
|
||||
testSecureCibaSessionEnforceExecutor(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureCibaSessionEnforceExecutorWithSignedAuthenticationRequestUriParam() throws Exception {
|
||||
testSecureCibaSessionEnforceExecutor(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackchannelAuthenticationFlowWithInvalidSignedAuthenticationRequest() throws Exception {
|
||||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
try {
|
||||
boolean useRequestUri = false;
|
||||
String sigAlg = Algorithm.PS256;
|
||||
final String username = "nutzername-rot";
|
||||
String bindingMessage = "Flughafen-Frankfurt-am-Main";
|
||||
|
||||
// prepare CIBA settings
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm(TEST_REALM_NAME), TEST_CLIENT_NAME);
|
||||
clientRep = clientResource.toRepresentation();
|
||||
prepareCIBASettings(clientResource, clientRep);
|
||||
|
||||
// register profiles
|
||||
String json = (new ClientProfilesBuilder()).addProfile(
|
||||
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
|
||||
.addExecutor(SecureCibaSignedAuthenticationRequestExecutorFactory.PROVIDER_ID, null)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updateProfiles(json);
|
||||
|
||||
// register policies
|
||||
json = (new ClientPoliciesBuilder()).addPolicy(
|
||||
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
|
||||
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
|
||||
createAnyClientConditionConfig())
|
||||
.addProfile(PROFILE_NAME)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updatePolicies(json);
|
||||
|
||||
AuthorizationEndpointRequestObject requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
|
||||
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
AuthenticationRequestAcknowledgement response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
|
||||
assertThat(response.getStatusCode(), is(equalTo(400)));
|
||||
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
|
||||
assertThat(response.getErrorDescription(), is("Missing parameter in the signed authentication request: exp"));
|
||||
|
||||
useRequestUri = true;
|
||||
bindingMessage = "Flughafen-Wien-Schwechat";
|
||||
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
|
||||
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
|
||||
|
||||
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
|
||||
assertThat(response.getStatusCode(), is(equalTo(400)));
|
||||
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
|
||||
assertThat(response.getErrorDescription(), is("Missing parameter in the signed authentication request: nbf"));
|
||||
|
||||
useRequestUri = false;
|
||||
bindingMessage = "Stuttgart-Hauptbahnhof";
|
||||
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
|
||||
requestObject.exp(requestObject.getIat() + SecureCibaSignedAuthenticationRequestExecutor.DEFAULT_AVAILABLE_PERIOD + 10);
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
|
||||
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null, null);
|
||||
assertThat(response.getStatusCode(), is(equalTo(400)));
|
||||
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
|
||||
assertThat(response.getErrorDescription(), is("signed authentication request's available period is long"));
|
||||
|
||||
useRequestUri = true;
|
||||
bindingMessage = "Brno-hlavni-nadrazif";
|
||||
requestObject = createPartialAuthorizationEndpointRequestObject(username, bindingMessage);
|
||||
requestObject.exp(requestObject.getIat() + Long.valueOf(300));
|
||||
requestObject.nbf(requestObject.getIat());
|
||||
|
||||
registerSharedAuthenticationRequest(requestObject, TEST_CLIENT_NAME, sigAlg, useRequestUri);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
response = doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage);
|
||||
|
||||
// user Authentication Channel Request
|
||||
TestAuthenticationChannelRequest testRequest = doAuthenticationChannelRequest(bindingMessage);
|
||||
AuthenticationChannelRequest authenticationChannelReq = testRequest.getRequest();
|
||||
assertThat(authenticationChannelReq.getBindingMessage(), is(equalTo(bindingMessage)));
|
||||
assertThat(authenticationChannelReq.getScope(), is(containsString(OAuth2Constants.SCOPE_OPENID)));
|
||||
|
||||
// user Authentication Channel completed
|
||||
doAuthenticationChannelCallback(testRequest);
|
||||
|
||||
// user Token Request
|
||||
doBackchannelAuthenticationTokenRequest(username, response.getAuthReqId());
|
||||
} finally {
|
||||
revertCIBASettings(clientResource, clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthorizationEndpointRequestObject createPartialAuthorizationEndpointRequestObject(String username, String bindingMessage) throws Exception {
|
||||
AuthorizationEndpointRequestObject requestObject = new AuthorizationEndpointRequestObject();
|
||||
requestObject.id(org.keycloak.models.utils.KeycloakModelUtils.generateId());
|
||||
requestObject.iat(Long.valueOf(Time.currentTime()));
|
||||
requestObject.setScope("openid");
|
||||
requestObject.setMax_age(Integer.valueOf(600));
|
||||
requestObject.setOtherClaims("custom_claim_zwei", "gelb");
|
||||
requestObject.audience(Urls.realmIssuer(new URI(suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth"), TEST_REALM_NAME), "https://example.com");
|
||||
requestObject.setLoginHint(username);
|
||||
requestObject.setBindingMessage(bindingMessage);
|
||||
return requestObject;
|
||||
}
|
||||
|
||||
private void testSecureCibaSessionEnforceExecutor(boolean useRequestUri) throws Exception {
|
||||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
try {
|
||||
String sigAlg = Algorithm.PS256;
|
||||
final String username = "nutzername-rot";
|
||||
|
||||
// prepare CIBA settings
|
||||
clientResource = ApiUtil.findClientByClientId(adminClient.realm(TEST_REALM_NAME), TEST_CLIENT_NAME);
|
||||
clientRep = clientResource.toRepresentation();
|
||||
prepareCIBASettings(clientResource, clientRep);
|
||||
|
||||
AuthorizationEndpointRequestObject sharedAuthenticationRequest = createValidSharedAuthenticationRequest();
|
||||
sharedAuthenticationRequest.setLoginHint(username);
|
||||
registerSharedAuthenticationRequest(sharedAuthenticationRequest, TEST_CLIENT_NAME, sigAlg, useRequestUri);
|
||||
|
||||
// register profiles
|
||||
String json = (new ClientProfilesBuilder()).addProfile(
|
||||
(new ClientProfileBuilder()).createProfile(PROFILE_NAME, "Le Premier Profil")
|
||||
.addExecutor(SecureCibaSessionEnforceExecutorFactory.PROVIDER_ID, null)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updateProfiles(json);
|
||||
|
||||
// register policies
|
||||
json = (new ClientPoliciesBuilder()).addPolicy(
|
||||
(new ClientPolicyBuilder()).createPolicy(POLICY_NAME, "La Premiere Politique", Boolean.TRUE)
|
||||
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
|
||||
createAnyClientConditionConfig())
|
||||
.addProfile(PROFILE_NAME)
|
||||
.toRepresentation()
|
||||
).toString();
|
||||
updatePolicies(json);
|
||||
|
||||
// user Backchannel Authentication Request
|
||||
AuthenticationRequestAcknowledgement response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, null, null, null);
|
||||
assertThat(response.getStatusCode(), is(equalTo(400)));
|
||||
assertThat(response.getError(), is(OAuthErrorException.INVALID_REQUEST));
|
||||
assertThat(response.getErrorDescription(), is("Missing parameter: binding_message"));
|
||||
|
||||
} finally {
|
||||
revertCIBASettings(clientResource, clientRep);
|
||||
}
|
||||
}
|
||||
|
||||
private RealmResource testRealm() {
|
||||
return adminClient.realm(REALM_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackchannelAuthenticationFlowRegisterDifferentSigAlgInAdvanceWithSignedAuthenticationRequestParam() throws Exception {
|
||||
testBackchannelAuthenticationFlowWithInvalidSignedAuthenticationRequest(false, Algorithm.ES256, Algorithm.PS256, 400, OAuthErrorException.INVALID_REQUEST, "Client requested algorithm not registered in advance or request signed with different algorithm other than client requested algorithm", TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD);
|
||||
|
@ -1383,31 +1589,6 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
private String createClientDynamically(String clientName, Consumer<OIDCClientRepresentation> op) throws ClientRegistrationException {
|
||||
OIDCClientRepresentation clientRep = new OIDCClientRepresentation();
|
||||
clientRep.setClientName(clientName);
|
||||
clientRep.setClientUri(ServerURLs.getAuthServerContextRoot());
|
||||
clientRep.setRedirectUris(Collections.singletonList(ServerURLs.getAuthServerContextRoot() + "/auth/realms/master/app/auth"));
|
||||
op.accept(clientRep);
|
||||
OIDCClientRepresentation response = reg.oidc().create(clientRep);
|
||||
reg.auth(Auth.token(response));
|
||||
// registered components will be removed automatically when a test method finishes regardless of its success or failure.
|
||||
String clientId = response.getClientId();
|
||||
testContext.getOrCreateCleanup(TEST_REALM_NAME).addClientUuid(clientId);
|
||||
return clientId;
|
||||
}
|
||||
|
||||
private OIDCClientRepresentation getClientDynamically(String clientId) throws ClientRegistrationException {
|
||||
return reg.oidc().get(clientId);
|
||||
}
|
||||
|
||||
protected void updateClientDynamically(String clientId, Consumer<OIDCClientRepresentation> op) throws ClientRegistrationException {
|
||||
OIDCClientRepresentation clientRep = reg.oidc().get(clientId);
|
||||
op.accept(clientRep);
|
||||
OIDCClientRepresentation response = reg.oidc().update(clientRep);
|
||||
reg.auth(Auth.token(response));
|
||||
}
|
||||
|
||||
private void testAuthenticationChannelErrorCase(Status statusCallback, Status statusTokenEndpont, AuthenticationChannelResponse.Status authStatus, String error, String errorEvent) throws Exception {
|
||||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
|
@ -1650,11 +1831,14 @@ public class CIBATest extends AbstractTestRealmKeycloakTest {
|
|||
}
|
||||
|
||||
private void testBackchannelAuthenticationFlow(boolean isOfflineAccess) throws Exception {
|
||||
testBackchannelAuthenticationFlow(isOfflineAccess, "BASTION");
|
||||
}
|
||||
|
||||
private void testBackchannelAuthenticationFlow(boolean isOfflineAccess, String bindingMessage) throws Exception {
|
||||
ClientResource clientResource = null;
|
||||
ClientRepresentation clientRep = null;
|
||||
try {
|
||||
final String username = "nutzername-rot";
|
||||
final String bindingMessage = "BASTION";
|
||||
Map<String, String> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put("user_device", "mobile");
|
||||
|
||||
|
|
Loading…
Reference in a new issue