7cff857238
--- Quarkus3 branch sync no. 14 (24.4.2023) Resolved conflicts: keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ComponentExportImportTest.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/DeclarativeUserTest.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/FederatedStorageExportImportTest.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java - Modified keycloak/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java - Modified --- Quarkus3 branch sync no. 13 (11.4.2023) Resolved conflicts: keycloak/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountTotpPage.java - Deleted keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java - Modified --- Quarkus3 branch sync no. 12 (31.3.2023) Resolved conflicts: keycloak/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/resources/QuarkusWelcomeResource.java - Modified keycloak/services/src/main/java/org/keycloak/protocol/saml/profile/util/Soap.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java - Modified keycloak/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/sessionlimits/UserSessionLimitsTest.java - Modified --- Quarkus3 branch sync no. 10 (17.3.2023) Resolved conflicts: keycloak/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java - Modified --- Quarkus3 branch sync no. 9 (10.3.2023) Resolved conflicts: keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosSingleRealmTest.java - Modified keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java - Modified --- Quarkus3 branch sync no. 8 (3.3.2023) Resolved conflicts: keycloak/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SamlClient.java Modified - Modified keycloak/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java - Modified keycloak/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java - Modified --- Quarkus3 branch sync no. 6 (17.2.2023) Resolved conflicts: keycloak/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ComponentsResource.java - Modified keycloak/testsuite/utils/src/main/java/org/keycloak/testsuite/KeycloakServer.java - Modified keycloak/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java - Modified --- Quarkus3 branch sync no. 5 (10.2.2023) Resolved conflicts: /keycloak/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java Modified - Modified keycloak/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java - Modified --- Quarkus3 branch sync no. 4 (3.2.2023) Resolved conflicts: keycloak/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/jaxrs/QuarkusKeycloakApplication.java - Modified --- Quarkus3 branch sync no. 1 (18.1.2023) Resolved conflicts: keycloak/testsuite/client/ClientPoliciesTest.java - Deleted keycloak/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java - Modified keycloak/model/map-jpa/src/main/java/org/keycloak/models/map/storage/jpa/JpaModelCriteriaBuilder.java - Modified
198 lines
8.2 KiB
Java
Executable file
198 lines
8.2 KiB
Java
Executable file
/*
|
|
* Copyright 2016 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.authentication;
|
|
|
|
import org.jboss.logging.Logger;
|
|
import org.keycloak.events.Details;
|
|
import org.keycloak.events.Errors;
|
|
import org.keycloak.models.AuthenticationExecutionModel;
|
|
import org.keycloak.models.AuthenticationFlowModel;
|
|
import org.keycloak.models.ClientModel;
|
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
import org.keycloak.services.ServicesLogger;
|
|
|
|
import jakarta.ws.rs.core.Response;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
|
|
/**
|
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
*/
|
|
public class ClientAuthenticationFlow implements AuthenticationFlow {
|
|
|
|
private static final Logger logger = Logger.getLogger(ClientAuthenticationFlow.class);
|
|
|
|
Response alternativeChallenge = null;
|
|
AuthenticationProcessor processor;
|
|
AuthenticationFlowModel flow;
|
|
|
|
private boolean success;
|
|
|
|
public ClientAuthenticationFlow(AuthenticationProcessor processor, AuthenticationFlowModel flow) {
|
|
this.processor = processor;
|
|
this.flow = flow;
|
|
}
|
|
|
|
@Override
|
|
public Response processAction(String actionExecution) {
|
|
throw new IllegalStateException("Not supposed to be invoked");
|
|
}
|
|
|
|
@Override
|
|
public Response processFlow() {
|
|
List<AuthenticationExecutionModel> executions = findExecutionsToRun();
|
|
|
|
for (AuthenticationExecutionModel model : executions) {
|
|
ClientAuthenticatorFactory factory = (ClientAuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, model.getAuthenticator());
|
|
if (factory == null) {
|
|
throw new AuthenticationFlowException("Could not find ClientAuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationFlowError.INTERNAL_ERROR);
|
|
}
|
|
ClientAuthenticator authenticator = factory.create();
|
|
logger.debugv("client authenticator: {0}", factory.getId());
|
|
|
|
AuthenticationProcessor.Result context = processor.createClientAuthenticatorContext(model, authenticator, executions);
|
|
authenticator.authenticateClient(context);
|
|
|
|
ClientModel client = processor.getClient();
|
|
if (client != null) {
|
|
String expectedClientAuthType = client.getClientAuthenticatorType();
|
|
|
|
// Fallback to secret just in case (for backwards compatibility). Also for public clients, ignore the "clientAuthenticatorType", which is set to them and stick to the
|
|
// default, which set the client just based on "client_id" parameter
|
|
if (expectedClientAuthType == null || client.isPublicClient()) {
|
|
if (expectedClientAuthType == null) {
|
|
ServicesLogger.LOGGER.authMethodFallback(client.getClientId(), expectedClientAuthType);
|
|
}
|
|
expectedClientAuthType = KeycloakModelUtils.getDefaultClientAuthenticatorType();
|
|
}
|
|
|
|
// Check if client authentication matches
|
|
if (factory.getId().equals(expectedClientAuthType)) {
|
|
Response response = processResult(context);
|
|
if (response != null) return response;
|
|
|
|
if (!context.getStatus().equals(FlowStatus.SUCCESS)) {
|
|
throw new AuthenticationFlowException("Expected success, but for an unknown reason the status was " + context.getStatus(), AuthenticationFlowError.INTERNAL_ERROR);
|
|
} else {
|
|
success = true;
|
|
}
|
|
|
|
logger.debugv("Client {0} authenticated by {1}", client.getClientId(), factory.getId());
|
|
processor.getEvent().detail(Details.CLIENT_AUTH_METHOD, factory.getId());
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if any alternative challenge was identified
|
|
if (alternativeChallenge != null) {
|
|
processor.getEvent().error(Errors.INVALID_CLIENT);
|
|
return alternativeChallenge;
|
|
}
|
|
|
|
throw new AuthenticationFlowException("Invalid client or Invalid client credentials", AuthenticationFlowError.CLIENT_NOT_FOUND);
|
|
}
|
|
|
|
protected List<AuthenticationExecutionModel> findExecutionsToRun() {
|
|
List<AuthenticationExecutionModel> executionsToRun = new LinkedList<>();
|
|
List<AuthenticationExecutionModel> finalExecutionsToRun = executionsToRun;
|
|
Optional<AuthenticationExecutionModel> first = processor.getRealm().getAuthenticationExecutionsStream(flow.getId())
|
|
.filter(e -> {
|
|
if (e.isRequired()) {
|
|
return true;
|
|
} else if (e.isAlternative()){
|
|
finalExecutionsToRun.add(e);
|
|
return false;
|
|
}
|
|
return false;
|
|
}).findFirst();
|
|
|
|
if (first.isPresent())
|
|
executionsToRun = Arrays.asList(first.get());
|
|
else
|
|
executionsToRun.addAll(finalExecutionsToRun);
|
|
|
|
if (logger.isTraceEnabled()) {
|
|
List<String> exIds = new ArrayList<>();
|
|
for (AuthenticationExecutionModel execution : executionsToRun) {
|
|
exIds.add(execution.getId());
|
|
}
|
|
logger.tracef("Using executions for client authentication: %s", exIds.toString());
|
|
}
|
|
|
|
return executionsToRun;
|
|
}
|
|
|
|
protected Response processResult(AuthenticationProcessor.Result result) {
|
|
AuthenticationExecutionModel execution = result.getExecution();
|
|
FlowStatus status = result.getStatus();
|
|
|
|
logger.debugv("client authenticator {0}: {1}", status.toString(), execution.getAuthenticator());
|
|
|
|
if (status == FlowStatus.SUCCESS) {
|
|
return null;
|
|
}
|
|
|
|
if (status == FlowStatus.FAILED) {
|
|
if (result.getChallenge() != null) {
|
|
return sendChallenge(result, execution);
|
|
} else {
|
|
throw new AuthenticationFlowException(result.getError());
|
|
}
|
|
} else if (status == FlowStatus.FORCE_CHALLENGE) {
|
|
return sendChallenge(result, execution);
|
|
} else if (status == FlowStatus.CHALLENGE) {
|
|
|
|
// Make sure the first priority alternative challenge is used
|
|
if (alternativeChallenge == null) {
|
|
alternativeChallenge = result.getChallenge();
|
|
}
|
|
return sendChallenge(result, execution);
|
|
} else if (status == FlowStatus.FAILURE_CHALLENGE) {
|
|
return sendChallenge(result, execution);
|
|
} else {
|
|
ServicesLogger.LOGGER.unknownResultStatus();
|
|
throw new AuthenticationFlowException(AuthenticationFlowError.INTERNAL_ERROR);
|
|
}
|
|
}
|
|
|
|
public Response sendChallenge(AuthenticationProcessor.Result result, AuthenticationExecutionModel execution) {
|
|
logger.debugv("client authenticator: sending challenge for authentication execution {0}", execution.getAuthenticator());
|
|
|
|
if (result.getError() != null) {
|
|
String errorAsString = result.getError().toString().toLowerCase();
|
|
result.getEvent().error(errorAsString);
|
|
} else {
|
|
if (result.getClient() == null) {
|
|
result.getEvent().error(Errors.INVALID_CLIENT);
|
|
} else {
|
|
result.getEvent().error(Errors.INVALID_CLIENT_CREDENTIALS);
|
|
}
|
|
}
|
|
|
|
return result.getChallenge();
|
|
}
|
|
|
|
@Override
|
|
public boolean isSuccessful() {
|
|
return success;
|
|
}
|
|
}
|