non-browser flow
This commit is contained in:
parent
548c0db0ae
commit
fe9dc4a28d
24 changed files with 625 additions and 137 deletions
|
@ -18,14 +18,17 @@ public class DefaultAuthenticationFlows {
|
||||||
public static final String REGISTRATION_FLOW = "registration";
|
public static final String REGISTRATION_FLOW = "registration";
|
||||||
public static final String REGISTRATION_FORM_FLOW = "registration form";
|
public static final String REGISTRATION_FORM_FLOW = "registration form";
|
||||||
public static final String BROWSER_FLOW = "browser";
|
public static final String BROWSER_FLOW = "browser";
|
||||||
|
public static final String DIRECT_GRANT_FLOW = "direct grant";
|
||||||
public static final String LOGIN_FORMS_FLOW = "forms";
|
public static final String LOGIN_FORMS_FLOW = "forms";
|
||||||
|
|
||||||
public static void addFlows(RealmModel realm) {
|
public static void addFlows(RealmModel realm) {
|
||||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
|
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
|
||||||
|
if (realm.getFlowByAlias(DIRECT_GRANT_FLOW) == null) directGrantFlow(realm, false);
|
||||||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||||
}
|
}
|
||||||
public static void migrateFlows(RealmModel realm) {
|
public static void migrateFlows(RealmModel realm) {
|
||||||
browserFlow(realm, true);
|
browserFlow(realm, true);
|
||||||
|
directGrantFlow(realm, true);
|
||||||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +126,55 @@ public class DefaultAuthenticationFlows {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void directGrantFlow(RealmModel realm, boolean migrate) {
|
||||||
|
AuthenticationFlowModel grant = new AuthenticationFlowModel();
|
||||||
|
grant.setAlias(DIRECT_GRANT_FLOW);
|
||||||
|
grant.setDescription("OpenID Connect Resource Owner Grant");
|
||||||
|
grant.setProviderId("basic-flow");
|
||||||
|
grant.setTopLevel(true);
|
||||||
|
grant.setBuiltIn(true);
|
||||||
|
grant = realm.addAuthenticationFlow(grant);
|
||||||
|
|
||||||
|
// username
|
||||||
|
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(grant.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
execution.setAuthenticator("direct-grant-validate-username");
|
||||||
|
execution.setPriority(10);
|
||||||
|
execution.setUserSetupAllowed(false);
|
||||||
|
execution.setAutheticatorFlow(false);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
// password
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(grant.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
if (migrate && !hasCredentialType(realm, RequiredCredentialModel.PASSWORD.getType())) {
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
|
||||||
|
}
|
||||||
|
execution.setAuthenticator("direct-grant-validate-password");
|
||||||
|
execution.setPriority(20);
|
||||||
|
execution.setUserSetupAllowed(false);
|
||||||
|
execution.setAutheticatorFlow(false);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
// otp
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(grant.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
|
||||||
|
if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
|
}
|
||||||
|
execution.setAuthenticator("direct-grant-validate-otp");
|
||||||
|
execution.setPriority(30);
|
||||||
|
execution.setUserSetupAllowed(false);
|
||||||
|
execution.setAutheticatorFlow(false);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static void browserFlow(RealmModel realm, boolean migrate) {
|
public static void browserFlow(RealmModel realm, boolean migrate) {
|
||||||
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
AuthenticationFlowModel browser = new AuthenticationFlowModel();
|
||||||
browser.setAlias(BROWSER_FLOW);
|
browser.setAlias(BROWSER_FLOW);
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.keycloak.authentication;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.AbstractFormAuthenticator;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
|
@ -561,9 +561,18 @@ public class AuthenticationProcessor {
|
||||||
validateUser(authUser);
|
validateUser(authUser);
|
||||||
AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
|
AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
|
||||||
Response challenge = authenticationFlow.processFlow();
|
Response challenge = authenticationFlow.processFlow();
|
||||||
if (challenge != null) return challenge;
|
return challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response attachSessionExecutionRequiredActions() {
|
||||||
|
attachSession();
|
||||||
|
return AuthenticationManager.actionRequired(session, userSession, clientSession, connection, request, uriInfo, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attachSession() {
|
||||||
String username = clientSession.getAuthenticatedUser().getUsername();
|
String username = clientSession.getAuthenticatedUser().getUsername();
|
||||||
|
String attemptedUsername = clientSession.getNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME);
|
||||||
|
if (attemptedUsername != null) username = attemptedUsername;
|
||||||
if (userSession == null) { // if no authenticator attached a usersession
|
if (userSession == null) { // if no authenticator attached a usersession
|
||||||
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null);
|
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null);
|
||||||
userSession.setState(UserSessionModel.State.LOGGING_IN);
|
userSession.setState(UserSessionModel.State.LOGGING_IN);
|
||||||
|
@ -573,8 +582,10 @@ public class AuthenticationProcessor {
|
||||||
event.user(userSession.getUser())
|
event.user(userSession.getUser())
|
||||||
.detail(Details.USERNAME, username)
|
.detail(Details.USERNAME, username)
|
||||||
.session(userSession);
|
.session(userSession);
|
||||||
|
}
|
||||||
|
|
||||||
return AuthenticationManager.actionRequired(session, userSession, clientSession, connection, request, uriInfo, event);
|
public void evaluateRequiredActionTriggers() {
|
||||||
|
AuthenticationManager.evaluateRequiredActionTriggers(session, userSession, clientSession, connection, request, uriInfo, event, realm, clientSession.getAuthenticatedUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response finishAuthentication() {
|
public Response finishAuthentication() {
|
||||||
|
|
|
@ -105,6 +105,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
|
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
|
||||||
|
if (factory == null) {
|
||||||
|
throw new AuthenticationProcessor.AuthException("Could not find AuthenticatorFactory for: " + model.getAuthenticator(), AuthenticationProcessor.Error.INTERNAL_ERROR);
|
||||||
|
}
|
||||||
Authenticator authenticator = factory.create();
|
Authenticator authenticator = factory.create();
|
||||||
AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
|
AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
|
||||||
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
import org.keycloak.authentication.AuthenticatorContext;
|
|
@ -1,10 +1,9 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
|
@ -1,11 +1,10 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
|
@ -1,10 +1,9 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
@ -8,24 +8,19 @@ import org.keycloak.authentication.AuthenticatorContext;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
|
||||||
import org.keycloak.models.CredentialValidationOutput;
|
import org.keycloak.models.CredentialValidationOutput;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.keycloak.util.HtmlUtils.escapeAttribute;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
|
@ -1,10 +1,9 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
@ -6,7 +6,6 @@ import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorContext;
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.login.LoginFormsProvider;
|
import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
|
@ -1,10 +1,9 @@
|
||||||
package org.keycloak.authentication.authenticators;
|
package org.keycloak.authentication.authenticators.browser;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.AuthenticatorFactory;
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticatorConfigModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.keycloak.authentication.authenticators.directgrant;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDirectGrantAuthenticator implements Authenticator, AuthenticatorFactory {
|
||||||
|
public Response errorResponse(int status, String error, String errorDescription) {
|
||||||
|
Map<String, String> e = new HashMap<String, String>();
|
||||||
|
e.put(OAuth2Constants.ERROR, error);
|
||||||
|
if (errorDescription != null) {
|
||||||
|
e.put(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
|
||||||
|
}
|
||||||
|
return Response.status(status).entity(e).type(MediaType.APPLICATION_JSON_TYPE).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void action(AuthenticatorContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator create() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authenticator create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.keycloak.authentication.authenticators.directgrant;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ValidateOTP extends AbstractDirectGrantAuthenticator {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ValidateOTP.class);
|
||||||
|
public static final String PROVIDER_ID = "direct-grant-validate-otp";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticatorContext context) {
|
||||||
|
if (!isConfigured(context.getSession(), context.getRealm(), context.getUser())) {
|
||||||
|
if (context.getExecution().isOptional()) {
|
||||||
|
context.attempted();
|
||||||
|
} else if (context.getExecution().isRequired()) {
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
|
String otp = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||||
|
if (otp == null) {
|
||||||
|
if (context.getUser() != null) {
|
||||||
|
context.getEvent().user(context.getUser());
|
||||||
|
}
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
credentials.add(UserCredentialModel.totp(otp));
|
||||||
|
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||||
|
if (!valid) {
|
||||||
|
context.getEvent().user(context.getUser());
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isConfigured(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return session.users().configuredForCredentialType(UserCredentialModel.TOTP, realm, user) && user.isTotp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "OTP";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||||
|
AuthenticationExecutionModel.Requirement.OPTIONAL,
|
||||||
|
AuthenticationExecutionModel.Requirement.DISABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Validates the one time password supplied as a 'totp' form parameter in direct grant request";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package org.keycloak.authentication.authenticators.directgrant;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
|
import org.keycloak.authentication.authenticators.browser.AbstractFormAuthenticator;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ValidatePassword extends AbstractDirectGrantAuthenticator {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ValidatePassword.class);
|
||||||
|
public static final String PROVIDER_ID = "direct-grant-validate-password";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticatorContext context) {
|
||||||
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||||
|
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||||
|
if (password == null) {
|
||||||
|
if (context.getUser() != null) {
|
||||||
|
context.getEvent().user(context.getUser());
|
||||||
|
}
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
credentials.add(UserCredentialModel.password(password));
|
||||||
|
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||||
|
if (!valid) {
|
||||||
|
context.getEvent().user(context.getUser());
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Password";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED,
|
||||||
|
AuthenticationExecutionModel.Requirement.DISABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Validates the password supplied as a 'password' form parameter in direct grant request";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package org.keycloak.authentication.authenticators.directgrant;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.authentication.Authenticator;
|
||||||
|
import org.keycloak.authentication.AuthenticatorContext;
|
||||||
|
import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
|
import org.keycloak.authentication.authenticators.browser.AbstractFormAuthenticator;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ValidateUsername extends AbstractDirectGrantAuthenticator {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(ValidateUsername.class);
|
||||||
|
public static final String PROVIDER_ID = "direct-grant-validate-username";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void authenticate(AuthenticatorContext context) {
|
||||||
|
MultivaluedMap<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||||
|
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
|
||||||
|
if (username == null) {
|
||||||
|
context.getEvent().error(Errors.USER_NOT_FOUND);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_request", "Missing parameter: username");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.getEvent().detail(Details.USERNAME, username);
|
||||||
|
context.getClientSession().setNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME, username);
|
||||||
|
|
||||||
|
UserModel user = null;
|
||||||
|
try {
|
||||||
|
user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
|
||||||
|
} catch (ModelDuplicateException mde) {
|
||||||
|
logger.error(mde.getMessage(), mde);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_request", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.isEnabled()) {
|
||||||
|
context.getEvent().user(user);
|
||||||
|
context.getEvent().error(Errors.USER_DISABLED);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_grant", "Account disabled");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (context.getRealm().isBruteForceProtected()) {
|
||||||
|
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
|
||||||
|
context.getEvent().user(user);
|
||||||
|
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
|
||||||
|
Response challengeResponse = errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_grant", "Account temporarily disabled");
|
||||||
|
context.failure(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.setUser(user);
|
||||||
|
context.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresUser() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayType() {
|
||||||
|
return "Username Validation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getReferenceCategory() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfigurable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
|
||||||
|
AuthenticationExecutionModel.Requirement.REQUIRED
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
|
||||||
|
return REQUIREMENT_CHOICES;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Validates the username supplied as a 'username' form parameter in direct grant request";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return new LinkedList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -277,6 +277,9 @@ public class AuthorizationEndpoint {
|
||||||
Response challenge = null;
|
Response challenge = null;
|
||||||
try {
|
try {
|
||||||
challenge = processor.authenticateOnly();
|
challenge = processor.authenticateOnly();
|
||||||
|
if (challenge == null) {
|
||||||
|
challenge = processor.attachSessionExecutionRequiredActions();
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return processor.handleBrowserException(e);
|
return processor.handleBrowserException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,13 @@ import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -17,6 +19,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.UserSessionProvider;
|
import org.keycloak.models.UserSessionProvider;
|
||||||
|
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
@ -307,46 +310,38 @@ public class TokenEndpoint {
|
||||||
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
||||||
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
|
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
|
||||||
|
|
||||||
String username = formParams.getFirst(AuthenticationManager.FORM_USERNAME);
|
if (client.isConsentRequired()) {
|
||||||
if (username == null) {
|
throw new ErrorResponseException("invalid_client", "Client requires user consent", Response.Status.BAD_REQUEST);
|
||||||
event.error(Errors.USERNAME_MISSING);
|
|
||||||
throw new ErrorResponseException("invalid_request", "Missing parameter: username", Response.Status.UNAUTHORIZED);
|
|
||||||
}
|
}
|
||||||
event.detail(Details.USERNAME, username);
|
|
||||||
|
|
||||||
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
|
|
||||||
if (user != null) event.user(user);
|
|
||||||
|
|
||||||
AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formParams);
|
|
||||||
Map<String, String> err;
|
|
||||||
|
|
||||||
switch (authenticationStatus) {
|
|
||||||
case SUCCESS:
|
|
||||||
break;
|
|
||||||
case ACCOUNT_TEMPORARILY_DISABLED:
|
|
||||||
case ACTIONS_REQUIRED:
|
|
||||||
event.error(Errors.USER_TEMPORARILY_DISABLED);
|
|
||||||
throw new ErrorResponseException("invalid_grant", "Account temporarily disabled", Response.Status.BAD_REQUEST);
|
|
||||||
case ACCOUNT_DISABLED:
|
|
||||||
event.error(Errors.USER_DISABLED);
|
|
||||||
throw new ErrorResponseException("invalid_grant", "Account disabled", Response.Status.BAD_REQUEST);
|
|
||||||
default:
|
|
||||||
event.error(Errors.INVALID_USER_CREDENTIALS);
|
|
||||||
throw new ErrorResponseException("invalid_grant", "Invalid user credentials", Response.Status.UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
|
||||||
|
|
||||||
UserSessionProvider sessions = session.sessions();
|
UserSessionProvider sessions = session.sessions();
|
||||||
|
|
||||||
UserSessionModel userSession = sessions.createUserSession(realm, user, username, clientConnection.getRemoteAddr(), "oauth_credentials", false, null, null);
|
|
||||||
event.session(userSession);
|
|
||||||
|
|
||||||
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
|
ClientSessionModel clientSession = sessions.createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
|
||||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
|
||||||
|
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.DIRECT_GRANT_FLOW);
|
||||||
|
String flowId = flow.getId();
|
||||||
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
|
processor.setClientSession(clientSession)
|
||||||
|
.setFlowId(flowId)
|
||||||
|
.setConnection(clientConnection)
|
||||||
|
.setEventBuilder(event)
|
||||||
|
.setProtector(authManager.getProtector())
|
||||||
|
.setRealm(realm)
|
||||||
|
.setSession(session)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setRequest(request);
|
||||||
|
Response challenge = processor.authenticateOnly();
|
||||||
|
if (challenge != null) return challenge;
|
||||||
|
processor.evaluateRequiredActionTriggers();
|
||||||
|
UserModel user = clientSession.getAuthenticatedUser();
|
||||||
|
if (user.getRequiredActions() != null && user.getRequiredActions().size() > 0) {
|
||||||
|
throw new ErrorResponseException("invalid_grant", "Account is not fully set up", Response.Status.BAD_REQUEST);
|
||||||
|
|
||||||
TokenManager.attachClientSession(userSession, clientSession);
|
}
|
||||||
|
processor.attachSession();
|
||||||
|
UserSessionModel userSession = processor.getUserSession();
|
||||||
|
|
||||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
|
AccessTokenResponse res = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSession)
|
||||||
.generateAccessToken(session, scope, client, user, userSession, clientSession)
|
.generateAccessToken(session, scope, client, user, userSession, clientSession)
|
||||||
|
@ -354,6 +349,7 @@ public class TokenEndpoint {
|
||||||
.generateIDToken()
|
.generateIDToken()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
||||||
event.success();
|
event.success();
|
||||||
|
|
||||||
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||||
|
|
|
@ -429,66 +429,7 @@ public class AuthenticationManager {
|
||||||
final UserModel user = userSession.getUser();
|
final UserModel user = userSession.getUser();
|
||||||
final ClientModel client = clientSession.getClient();
|
final ClientModel client = clientSession.getClient();
|
||||||
|
|
||||||
RequiredActionContext context = new RequiredActionContext() {
|
RequiredActionContext context = evaluateRequiredActionTriggers(session, userSession, clientSession, clientConnection, request, uriInfo, event, realm, user);
|
||||||
@Override
|
|
||||||
public EventBuilder getEvent() {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserModel getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RealmModel getRealm() {
|
|
||||||
return realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientSessionModel getClientSession() {
|
|
||||||
return clientSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UserSessionModel getUserSession() {
|
|
||||||
return userSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClientConnection getConnection() {
|
|
||||||
return clientConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UriInfo getUriInfo() {
|
|
||||||
return uriInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeycloakSession getSession() {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpRequest getHttpRequest() {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String generateAccessCode(String action) {
|
|
||||||
ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
|
|
||||||
code.setAction(action);
|
|
||||||
return code.getCode();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// see if any required actions need triggering, i.e. an expired password
|
|
||||||
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
|
||||||
if (!model.isEnabled()) continue;
|
|
||||||
RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
|
|
||||||
provider.evaluateTriggers(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
logger.debugv("processAccessCode: go to oauth page?: {0}", client.isConsentRequired());
|
logger.debugv("processAccessCode: go to oauth page?: {0}", client.isConsentRequired());
|
||||||
|
@ -554,6 +495,70 @@ public class AuthenticationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RequiredActionContext evaluateRequiredActionTriggers(final KeycloakSession session, final UserSessionModel userSession, final ClientSessionModel clientSession, final ClientConnection clientConnection, final HttpRequest request, final UriInfo uriInfo, final EventBuilder event, final RealmModel realm, final UserModel user) {
|
||||||
|
RequiredActionContext context = new RequiredActionContext() {
|
||||||
|
@Override
|
||||||
|
public EventBuilder getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientSessionModel getClientSession() {
|
||||||
|
return clientSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserSessionModel getUserSession() {
|
||||||
|
return userSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientConnection getConnection() {
|
||||||
|
return clientConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UriInfo getUriInfo() {
|
||||||
|
return uriInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycloakSession getSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpRequest getHttpRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String generateAccessCode(String action) {
|
||||||
|
ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
|
||||||
|
code.setAction(action);
|
||||||
|
return code.getCode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// see if any required actions need triggering, i.e. an expired password
|
||||||
|
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
||||||
|
if (!model.isEnabled()) continue;
|
||||||
|
RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId());
|
||||||
|
provider.evaluateTriggers(context);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) {
|
protected static AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -23,14 +23,10 @@ package org.keycloak.services.resources;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.jboss.resteasy.spi.InternalServerErrorException;
|
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.AuthenticatorUtil;
|
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
|
|
||||||
import org.keycloak.authentication.authenticators.UsernamePasswordFormFactory;
|
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailProvider;
|
import org.keycloak.email.EmailProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
|
@ -55,8 +51,6 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -84,7 +78,6 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import javax.ws.rs.ext.Providers;
|
import javax.ws.rs.ext.Providers;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,10 @@ import org.keycloak.authentication.AuthenticatorFactory;
|
||||||
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
|
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
|
||||||
import org.keycloak.authentication.FormAction;
|
import org.keycloak.authentication.FormAction;
|
||||||
import org.keycloak.authentication.FormActionFactory;
|
import org.keycloak.authentication.FormActionFactory;
|
||||||
import org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory;
|
|
||||||
import org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory;
|
|
||||||
import org.keycloak.authentication.authenticators.UsernamePasswordFormFactory;
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used to set an execution a state based on type.
|
* used to set an execution a state based on type.
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
org.keycloak.authentication.authenticators.CookieAuthenticatorFactory
|
org.keycloak.authentication.authenticators.browser.CookieAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.UsernamePasswordFormFactory
|
org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory
|
||||||
org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
|
org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory
|
||||||
org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory
|
org.keycloak.authentication.authenticators.browser.SpnegoAuthenticatorFactory
|
||||||
|
org.keycloak.authentication.authenticators.directgrant.ValidateOTP
|
||||||
|
org.keycloak.authentication.authenticators.directgrant.ValidatePassword
|
||||||
|
org.keycloak.authentication.authenticators.directgrant.ValidateUsername
|
|
@ -18,7 +18,7 @@ import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.adapters.HttpClientBuilder;
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
import org.keycloak.authentication.authenticators.SpnegoAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.SpnegoAuthenticator;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
import org.keycloak.federation.kerberos.CommonKerberosConfig;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
|
|
|
@ -86,7 +86,6 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.user(userId)
|
.user(userId)
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
|
||||||
.detail(Details.RESPONSE_TYPE, "token")
|
.detail(Details.RESPONSE_TYPE, "token")
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
|
@ -123,7 +122,6 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.session(accessToken.getSessionState())
|
.session(accessToken.getSessionState())
|
||||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
|
||||||
.detail(Details.RESPONSE_TYPE, "token")
|
.detail(Details.RESPONSE_TYPE, "token")
|
||||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||||
|
@ -178,7 +176,6 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
events.expectLogin()
|
events.expectLogin()
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.session((String) null)
|
.session((String) null)
|
||||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
|
||||||
.detail(Details.RESPONSE_TYPE, "token")
|
.detail(Details.RESPONSE_TYPE, "token")
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
.removeDetail(Details.REDIRECT_URI)
|
.removeDetail(Details.REDIRECT_URI)
|
||||||
|
@ -201,7 +198,6 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
||||||
.client("resource-owner")
|
.client("resource-owner")
|
||||||
.user((String) null)
|
.user((String) null)
|
||||||
.session((String) null)
|
.session((String) null)
|
||||||
.detail(Details.AUTH_METHOD, "oauth_credentials")
|
|
||||||
.detail(Details.RESPONSE_TYPE, "token")
|
.detail(Details.RESPONSE_TYPE, "token")
|
||||||
.detail(Details.USERNAME, "invalid")
|
.detail(Details.USERNAME, "invalid")
|
||||||
.removeDetail(Details.CODE_ID)
|
.removeDetail(Details.CODE_ID)
|
||||||
|
|
Loading…
Reference in a new issue