initial
This commit is contained in:
parent
5d74b776d6
commit
f4a5e49b63
15 changed files with 129 additions and 100 deletions
|
@ -35,7 +35,7 @@ import javax.ws.rs.core.Response;
|
||||||
* should abort with an error message.
|
* should abort with an error message.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TextChallenge {
|
public class ConsoleDisplayMode {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browser is required to login. This will abort client from doing a console login.
|
* Browser is required to login. This will abort client from doing a console login.
|
||||||
|
@ -50,6 +50,27 @@ public class TextChallenge {
|
||||||
.entity("\n" + session.getProvider(LoginFormsProvider.class).getMessage("browserRequired") + "\n").build();
|
.entity("\n" + session.getProvider(LoginFormsProvider.class).getMessage("browserRequired") + "\n").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browser is required to continue login. This will prompt client on whether to continue with a browser or abort.
|
||||||
|
*
|
||||||
|
* @param session
|
||||||
|
* @param callback
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Response browserContinue(KeycloakSession session, String callback) {
|
||||||
|
String browserContinueMsg = session.getProvider(LoginFormsProvider.class).getMessage("browserContinue");
|
||||||
|
String browserPrompt = session.getProvider(LoginFormsProvider.class).getMessage("browserContinuePrompt");
|
||||||
|
String answer = session.getProvider(LoginFormsProvider.class).getMessage("browserContinueAnswer");
|
||||||
|
|
||||||
|
String header = "X-Text-Form-Challenge callback=\"" + callback + "\"";
|
||||||
|
header += " browserContinue=\"" + browserPrompt + "\" answer=\"" + answer + "\"";
|
||||||
|
return Response.status(Response.Status.UNAUTHORIZED)
|
||||||
|
.header("WWW-Authenticate", header)
|
||||||
|
.type(MediaType.TEXT_PLAIN)
|
||||||
|
.entity("\n" + browserContinueMsg + "\n").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build challenge response for required actions
|
* Build challenge response for required actions
|
||||||
|
@ -57,8 +78,8 @@ public class TextChallenge {
|
||||||
* @param context
|
* @param context
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static TextChallenge challenge(RequiredActionContext context) {
|
public static ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||||
return new TextChallenge(context);
|
return new ConsoleDisplayMode(context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,8 +89,8 @@ public class TextChallenge {
|
||||||
* @param context
|
* @param context
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static TextChallenge challenge(AuthenticationFlowContext context) {
|
public static ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||||
return new TextChallenge(context);
|
return new ConsoleDisplayMode(context);
|
||||||
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -79,7 +100,7 @@ public class TextChallenge {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static HeaderBuilder header(RequiredActionContext context) {
|
public static HeaderBuilder header(RequiredActionContext context) {
|
||||||
return new TextChallenge(context).header();
|
return new ConsoleDisplayMode(context).header();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,14 +111,14 @@ public class TextChallenge {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static HeaderBuilder header(AuthenticationFlowContext context) {
|
public static HeaderBuilder header(AuthenticationFlowContext context) {
|
||||||
return new TextChallenge(context).header();
|
return new ConsoleDisplayMode(context).header();
|
||||||
|
|
||||||
}
|
}
|
||||||
TextChallenge(RequiredActionContext requiredActionContext) {
|
ConsoleDisplayMode(RequiredActionContext requiredActionContext) {
|
||||||
this.requiredActionContext = requiredActionContext;
|
this.requiredActionContext = requiredActionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextChallenge(AuthenticationFlowContext flowContext) {
|
ConsoleDisplayMode(AuthenticationFlowContext flowContext) {
|
||||||
this.flowContext = flowContext;
|
this.flowContext = flowContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,20 +299,20 @@ public class TextChallenge {
|
||||||
return HeaderBuilder.this.build();
|
return HeaderBuilder.this.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextChallenge challenge() {
|
public ConsoleDisplayMode challenge() {
|
||||||
return TextChallenge.this;
|
return ConsoleDisplayMode.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoginFormsProvider form() {
|
public LoginFormsProvider form() {
|
||||||
return TextChallenge.this.form();
|
return ConsoleDisplayMode.this.form();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response message(String msg, String... params) {
|
public Response message(String msg, String... params) {
|
||||||
return TextChallenge.this.message(msg, params);
|
return ConsoleDisplayMode.this.message(msg, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response text(String text) {
|
public Response text(String text) {
|
||||||
return TextChallenge.this.text(text);
|
return ConsoleDisplayMode.this.text(text);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,6 +254,19 @@ public class AuthenticationProcessor {
|
||||||
getAuthenticationSession().setAuthenticatedUser(null);
|
getAuthenticationSession().setAuthenticatedUser(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public URI getRefreshUrl(boolean authSessionIdParam) {
|
||||||
|
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
||||||
|
.path(AuthenticationProcessor.this.flowPath)
|
||||||
|
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
||||||
|
.queryParam(Constants.TAB_ID, getAuthenticationSession().getTabId());
|
||||||
|
if (authSessionIdParam) {
|
||||||
|
uriBuilder.queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId());
|
||||||
|
}
|
||||||
|
return uriBuilder
|
||||||
|
.build(getRealm().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public class Result implements AuthenticationFlowContext, ClientAuthenticationFlowContext {
|
public class Result implements AuthenticationFlowContext, ClientAuthenticationFlowContext {
|
||||||
AuthenticatorConfigModel authenticatorConfig;
|
AuthenticatorConfigModel authenticatorConfig;
|
||||||
AuthenticationExecutionModel execution;
|
AuthenticationExecutionModel execution;
|
||||||
|
@ -546,15 +559,7 @@ public class AuthenticationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getRefreshUrl(boolean authSessionIdParam) {
|
public URI getRefreshUrl(boolean authSessionIdParam) {
|
||||||
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(getUriInfo())
|
return AuthenticationProcessor.this.getRefreshUrl(authSessionIdParam);
|
||||||
.path(AuthenticationProcessor.this.flowPath)
|
|
||||||
.queryParam(Constants.CLIENT_ID, getAuthenticationSession().getClient().getClientId())
|
|
||||||
.queryParam(Constants.TAB_ID, getAuthenticationSession().getTabId());
|
|
||||||
if (authSessionIdParam) {
|
|
||||||
uriBuilder.queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId());
|
|
||||||
}
|
|
||||||
return uriBuilder
|
|
||||||
.build(getRealm().getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -70,7 +70,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|
||||||
}
|
}
|
||||||
// todo create a provider for handling lack of display support
|
// todo create a provider for handling lack of display support
|
||||||
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
||||||
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, TextChallenge.browserRequired(processor.getSession()));
|
processor.getAuthenticationSession().removeClientNote(OAuth2Constants.DISPLAY);
|
||||||
|
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED,
|
||||||
|
ConsoleDisplayMode.browserContinue(processor.getSession(), processor.getRefreshUrl(true).toString()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return factory.create(processor.getSession());
|
return factory.create(processor.getSession());
|
||||||
|
|
|
@ -65,6 +65,10 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RequiredActionFactory getFactory() {
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventBuilder getEvent() {
|
public EventBuilder getEvent() {
|
||||||
return eventBuilder;
|
return eventBuilder;
|
||||||
|
@ -170,7 +174,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
|
||||||
uri = UriBuilder.fromUri(uri).queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId()).build();
|
uri = UriBuilder.fromUri(uri).queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId()).build();
|
||||||
}
|
}
|
||||||
return uri;
|
return uri;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.keycloak.authentication.authenticators.console;
|
||||||
|
|
||||||
import org.keycloak.authentication.AuthenticationFlowContext;
|
import org.keycloak.authentication.AuthenticationFlowContext;
|
||||||
import org.keycloak.authentication.Authenticator;
|
import org.keycloak.authentication.Authenticator;
|
||||||
import org.keycloak.authentication.TextChallenge;
|
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||||
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticator;
|
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticator;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ public class ConsoleOTPFormAuthenticator extends OTPFormAuthenticator implements
|
||||||
return context.getActionUrl(context.generateAccessCode(), true);
|
return context.getActionUrl(context.generateAccessCode(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TextChallenge challenge(AuthenticationFlowContext context) {
|
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||||
return TextChallenge.challenge(context)
|
return ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param(CredentialRepresentation.TOTP)
|
.param(CredentialRepresentation.TOTP)
|
||||||
.label("console-otp")
|
.label("console-otp")
|
||||||
|
|
|
@ -24,11 +24,8 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -43,8 +40,8 @@ public class ConsoleUsernamePasswordAuthenticator extends AbstractUsernameFormAu
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TextChallenge challenge(AuthenticationFlowContext context) {
|
protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
|
||||||
return TextChallenge.challenge(context)
|
return ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param("username")
|
.param("username")
|
||||||
.label("console-username")
|
.label("console-username")
|
||||||
|
|
|
@ -17,14 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.authentication.requiredactions;
|
package org.keycloak.authentication.requiredactions;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionFactory;
|
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.authentication.TextChallenge;
|
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -45,7 +41,7 @@ public class ConsoleTermsAndConditions implements RequiredActionProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requiredActionChallenge(RequiredActionContext context) {
|
public void requiredActionChallenge(RequiredActionContext context) {
|
||||||
Response challenge = TextChallenge.challenge(context)
|
Response challenge = ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param("accept")
|
.param("accept")
|
||||||
.label("console-accept-terms")
|
.label("console-accept-terms")
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.keycloak.authentication.requiredactions;
|
package org.keycloak.authentication.requiredactions;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.*;
|
import org.keycloak.authentication.*;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -28,11 +27,7 @@ import org.keycloak.models.*;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -45,8 +40,8 @@ public class ConsoleUpdatePassword extends UpdatePassword implements RequiredAct
|
||||||
public static final String PASSWORD_NEW = "password-new";
|
public static final String PASSWORD_NEW = "password-new";
|
||||||
public static final String PASSWORD_CONFIRM = "password-confirm";
|
public static final String PASSWORD_CONFIRM = "password-confirm";
|
||||||
|
|
||||||
protected TextChallenge challenge(RequiredActionContext context) {
|
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||||
return TextChallenge.challenge(context)
|
return ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param(PASSWORD_NEW)
|
.param(PASSWORD_NEW)
|
||||||
.label("console-new-password")
|
.label("console-new-password")
|
||||||
|
|
|
@ -17,28 +17,19 @@
|
||||||
|
|
||||||
package org.keycloak.authentication.requiredactions;
|
package org.keycloak.authentication.requiredactions;
|
||||||
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionFactory;
|
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.authentication.TextChallenge;
|
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.forms.login.LoginFormsProvider;
|
|
||||||
import org.keycloak.forms.login.freemarker.model.TotpBean;
|
import org.keycloak.forms.login.freemarker.model.TotpBean;
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -61,8 +52,8 @@ public class ConsoleUpdateTotp implements RequiredActionProvider {
|
||||||
context.challenge(challenge);
|
context.challenge(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TextChallenge challenge(RequiredActionContext context) {
|
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||||
return TextChallenge.challenge(context)
|
return ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param("totp")
|
.param("totp")
|
||||||
.label("console-otp")
|
.label("console-otp")
|
||||||
|
|
|
@ -18,35 +18,24 @@
|
||||||
package org.keycloak.authentication.requiredactions;
|
package org.keycloak.authentication.requiredactions;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.authentication.RequiredActionContext;
|
import org.keycloak.authentication.RequiredActionContext;
|
||||||
import org.keycloak.authentication.RequiredActionFactory;
|
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.authentication.TextChallenge;
|
import org.keycloak.authentication.ConsoleDisplayMode;
|
||||||
import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
|
|
||||||
import org.keycloak.common.util.RandomString;
|
import org.keycloak.common.util.RandomString;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
import org.keycloak.email.EmailException;
|
import org.keycloak.email.EmailException;
|
||||||
import org.keycloak.email.EmailTemplateProvider;
|
import org.keycloak.email.EmailTemplateProvider;
|
||||||
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.forms.login.LoginFormsProvider;
|
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.sessions.AuthenticationSessionCompoundId;
|
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.*;
|
import javax.ws.rs.core.*;
|
||||||
import java.net.URI;
|
|
||||||
import java.text.MessageFormat;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -114,8 +103,8 @@ public class ConsoleVerifyEmail implements RequiredActionProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String EMAIL_CODE="email_code";
|
public static String EMAIL_CODE="email_code";
|
||||||
protected TextChallenge challenge(RequiredActionContext context) {
|
protected ConsoleDisplayMode challenge(RequiredActionContext context) {
|
||||||
return TextChallenge.challenge(context)
|
return ConsoleDisplayMode.challenge(context)
|
||||||
.header()
|
.header()
|
||||||
.param(EMAIL_CODE)
|
.param(EMAIL_CODE)
|
||||||
.label("console-email-code")
|
.label("console-email-code")
|
||||||
|
|
|
@ -966,21 +966,22 @@ public class AuthenticationManager {
|
||||||
authSession.setProtocolMappers(requestedProtocolMappers);
|
authSession.setProtocolMappers(requestedProtocolMappers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredActionProvider createRequiredAction(KeycloakSession session, RequiredActionFactory factory, AuthenticationSessionModel authSession) {
|
public static RequiredActionProvider createRequiredAction(RequiredActionContextResult context) {
|
||||||
String display = authSession.getClientNote(OAuth2Constants.DISPLAY);
|
String display = context.getAuthenticationSession().getClientNote(OAuth2Constants.DISPLAY);
|
||||||
if (display == null) return factory.create(session);
|
if (display == null) return context.getFactory().create(context.getSession());
|
||||||
|
|
||||||
|
|
||||||
if (factory instanceof DisplayTypeRequiredActionFactory) {
|
if (context.getFactory() instanceof DisplayTypeRequiredActionFactory) {
|
||||||
RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)factory).createDisplay(session, display);
|
RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)context.getFactory()).createDisplay(context.getSession(), display);
|
||||||
if (provider != null) return provider;
|
if (provider != null) return provider;
|
||||||
}
|
}
|
||||||
// todo create a provider for handling lack of display support
|
// todo create a provider for handling lack of display support
|
||||||
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
|
||||||
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, TextChallenge.browserRequired(session));
|
context.getAuthenticationSession().removeClientNote(OAuth2Constants.DISPLAY);
|
||||||
|
throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, ConsoleDisplayMode.browserContinue(context.getSession(), context.getUriInfo().getRequestUri().toString()));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return factory.create(session);
|
return context.getFactory().create(context.getSession());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1002,16 +1003,16 @@ public class AuthenticationManager {
|
||||||
if (factory == null) {
|
if (factory == null) {
|
||||||
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
||||||
}
|
}
|
||||||
|
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
|
||||||
RequiredActionProvider actionProvider = null;
|
RequiredActionProvider actionProvider = null;
|
||||||
try {
|
try {
|
||||||
actionProvider = createRequiredAction(session, factory, authSession);
|
actionProvider = createRequiredAction(context);
|
||||||
} catch (AuthenticationFlowException e) {
|
} catch (AuthenticationFlowException e) {
|
||||||
if (e.getResponse() != null) {
|
if (e.getResponse() != null) {
|
||||||
return e.getResponse();
|
return e.getResponse();
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
|
|
||||||
actionProvider.requiredActionChallenge(context);
|
actionProvider.requiredActionChallenge(context);
|
||||||
|
|
||||||
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
|
||||||
|
|
|
@ -929,9 +929,15 @@ public class LoginActionsService {
|
||||||
event.error(Errors.INVALID_CODE);
|
event.error(Errors.INVALID_CODE);
|
||||||
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_CODE));
|
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_CODE));
|
||||||
}
|
}
|
||||||
|
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, authSession.getAuthenticatedUser(), factory) {
|
||||||
|
@Override
|
||||||
|
public void ignore() {
|
||||||
|
throw new RuntimeException("Cannot call ignore within processAction()");
|
||||||
|
}
|
||||||
|
};
|
||||||
RequiredActionProvider provider = null;
|
RequiredActionProvider provider = null;
|
||||||
try {
|
try {
|
||||||
provider = AuthenticationManager.createRequiredAction(session, factory, authSession);
|
provider = AuthenticationManager.createRequiredAction(context);
|
||||||
} catch (AuthenticationFlowException e) {
|
} catch (AuthenticationFlowException e) {
|
||||||
if (e.getResponse() != null) {
|
if (e.getResponse() != null) {
|
||||||
return e.getResponse();
|
return e.getResponse();
|
||||||
|
@ -939,12 +945,6 @@ public class LoginActionsService {
|
||||||
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED));
|
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED));
|
||||||
}
|
}
|
||||||
|
|
||||||
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, authSession.getAuthenticatedUser(), factory) {
|
|
||||||
@Override
|
|
||||||
public void ignore() {
|
|
||||||
throw new RuntimeException("Cannot call ignore within processAction()");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Response response;
|
Response response;
|
||||||
provider.processAction(context);
|
provider.processAction(context);
|
||||||
|
|
|
@ -306,14 +306,7 @@ public class AuthenticationManagementResource {
|
||||||
logger.debug("flow not found: " + flowAlias);
|
logger.debug("flow not found: " + flowAlias);
|
||||||
return Response.status(NOT_FOUND).build();
|
return Response.status(NOT_FOUND).build();
|
||||||
}
|
}
|
||||||
AuthenticationFlowModel copy = new AuthenticationFlowModel();
|
AuthenticationFlowModel copy = copyFlow(realm, flow, newName);
|
||||||
copy.setAlias(newName);
|
|
||||||
copy.setDescription(flow.getDescription());
|
|
||||||
copy.setProviderId(flow.getProviderId());
|
|
||||||
copy.setBuiltIn(false);
|
|
||||||
copy.setTopLevel(flow.isTopLevel());
|
|
||||||
copy = realm.addAuthenticationFlow(copy);
|
|
||||||
copy(newName, flow, copy);
|
|
||||||
|
|
||||||
data.put("id", copy.getId());
|
data.put("id", copy.getId());
|
||||||
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success();
|
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success();
|
||||||
|
@ -322,7 +315,19 @@ public class AuthenticationManagementResource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void copy(String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) {
|
public static AuthenticationFlowModel copyFlow(RealmModel realm, AuthenticationFlowModel flow, String newName) {
|
||||||
|
AuthenticationFlowModel copy = new AuthenticationFlowModel();
|
||||||
|
copy.setAlias(newName);
|
||||||
|
copy.setDescription(flow.getDescription());
|
||||||
|
copy.setProviderId(flow.getProviderId());
|
||||||
|
copy.setBuiltIn(false);
|
||||||
|
copy.setTopLevel(flow.isTopLevel());
|
||||||
|
copy = realm.addAuthenticationFlow(copy);
|
||||||
|
copy(realm, newName, flow, copy);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copy(RealmModel realm, String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) {
|
||||||
for (AuthenticationExecutionModel execution : realm.getAuthenticationExecutions(from.getId())) {
|
for (AuthenticationExecutionModel execution : realm.getAuthenticationExecutions(from.getId())) {
|
||||||
if (execution.isAuthenticatorFlow()) {
|
if (execution.isAuthenticatorFlow()) {
|
||||||
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
|
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
|
||||||
|
@ -334,7 +339,7 @@ public class AuthenticationManagementResource {
|
||||||
copy.setTopLevel(false);
|
copy.setTopLevel(false);
|
||||||
copy = realm.addAuthenticationFlow(copy);
|
copy = realm.addAuthenticationFlow(copy);
|
||||||
execution.setFlowId(copy.getId());
|
execution.setFlowId(copy.getId());
|
||||||
copy(newName, subFlow, copy);
|
copy(realm, newName, subFlow, copy);
|
||||||
}
|
}
|
||||||
execution.setId(null);
|
execution.setId(null);
|
||||||
execution.setParentFlow(to.getId());
|
execution.setParentFlow(to.getId());
|
||||||
|
|
|
@ -31,14 +31,18 @@ import org.keycloak.authorization.model.Policy;
|
||||||
import org.keycloak.authorization.model.ResourceServer;
|
import org.keycloak.authorization.model.ResourceServer;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
|
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
|
import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation;
|
||||||
|
import org.keycloak.services.resources.admin.AuthenticationManagementResource;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
|
||||||
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
import org.keycloak.testsuite.AssertEvents;
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.authentication.PushButtonAuthenticator;
|
||||||
|
import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
|
||||||
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
|
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
|
||||||
import org.keycloak.testsuite.pages.AppPage;
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
import org.keycloak.testsuite.pages.ErrorPage;
|
import org.keycloak.testsuite.pages.ErrorPage;
|
||||||
|
@ -98,6 +102,7 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
||||||
kcinit.setSecret("password");
|
kcinit.setSecret("password");
|
||||||
kcinit.setEnabled(true);
|
kcinit.setEnabled(true);
|
||||||
kcinit.addRedirectUri("urn:ietf:wg:oauth:2.0:oob");
|
kcinit.addRedirectUri("urn:ietf:wg:oauth:2.0:oob");
|
||||||
|
kcinit.addRedirectUri("http://localhost:*");
|
||||||
kcinit.setPublicClient(false);
|
kcinit.setPublicClient(false);
|
||||||
|
|
||||||
ClientModel app = realm.addClient(APP);
|
ClientModel app = realm.addClient(APP);
|
||||||
|
@ -154,13 +159,25 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
||||||
execution.setParentFlow(browser.getId());
|
execution.setParentFlow(browser.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||||
execution.setPriority(20);
|
execution.setPriority(20);
|
||||||
execution.setAuthenticator(PassThroughAuthenticator.PROVIDER_ID);
|
execution.setAuthenticator(PushButtonAuthenticatorFactory.PROVIDER_ID);
|
||||||
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
|
AuthenticationFlowModel browserBuiltin = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW);
|
||||||
|
AuthenticationFlowModel copy = AuthenticationManagementResource.copyFlow(realm, browserBuiltin, "copy-browser");
|
||||||
|
copy.setTopLevel(false);
|
||||||
|
realm.updateAuthenticationFlow(copy);
|
||||||
|
execution = new AuthenticationExecutionModel();
|
||||||
|
execution.setParentFlow(browser.getId());
|
||||||
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
|
||||||
|
execution.setFlowId(copy.getId());
|
||||||
|
execution.setPriority(30);
|
||||||
|
execution.setAuthenticatorFlow(true);
|
||||||
realm.addAuthenticatorExecution(execution);
|
realm.addAuthenticatorExecution(execution);
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//@Test
|
@Test
|
||||||
public void testDemo() throws Exception {
|
public void testDemo() throws Exception {
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
RealmModel realm = session.realms().getRealmByName("test");
|
RealmModel realm = session.realms().getRealmByName("test");
|
||||||
|
@ -193,7 +210,9 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Thread.sleep(100000000);
|
||||||
|
|
||||||
|
/*
|
||||||
testInstall();
|
testInstall();
|
||||||
// login
|
// login
|
||||||
//System.out.println("login....");
|
//System.out.println("login....");
|
||||||
|
@ -204,6 +223,7 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
||||||
Assert.assertEquals(1, exe.exitCode());
|
Assert.assertEquals(1, exe.exitCode());
|
||||||
Assert.assertTrue(exe.stderrString().contains("Browser required to login"));
|
Assert.assertTrue(exe.stderrString().contains("Browser required to login"));
|
||||||
//Assert.assertEquals("stderr first line", "Browser required to login", exe.stderrLines().get(1));
|
//Assert.assertEquals("stderr first line", "Browser required to login", exe.stderrLines().get(1));
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
testingClient.server().run(session -> {
|
testingClient.server().run(session -> {
|
||||||
|
|
|
@ -36,6 +36,10 @@ codeSuccessTitle=Success code
|
||||||
codeErrorTitle=Error code\: {0}
|
codeErrorTitle=Error code\: {0}
|
||||||
displayUnsupported=Requested display type unsupported
|
displayUnsupported=Requested display type unsupported
|
||||||
browserRequired=Browser required to login
|
browserRequired=Browser required to login
|
||||||
|
browserContinue=Browser required to complete login
|
||||||
|
browserContinuePrompt=Open browser and continue login? [y/n]:
|
||||||
|
browserContinueAnswer=y
|
||||||
|
|
||||||
|
|
||||||
termsTitle=Terms and Conditions
|
termsTitle=Terms and Conditions
|
||||||
termsText=<p>Terms and conditions to be defined</p>
|
termsText=<p>Terms and conditions to be defined</p>
|
||||||
|
|
Loading…
Reference in a new issue