keycloak-memoizeit
Changes
server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java 12(+12 -0)
server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeAuthenticatorFactory.java 21(+21 -0)
server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeRequiredActionFactory.java 13(+13 -0)
services/src/main/java/org/keycloak/authentication/authenticators/AttemptedAuthenticator.java 46(+46 -0)
services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java 12(+11 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java 12(+11 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java 10(+0 -10)
services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java 12(+11 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java 11(+10 -1)
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java 10(+0 -10)
services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java 12(+11 -1)
services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleOTPFormAuthenticator.java 3(+0 -3)
services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java 31(+2 -29)
services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java 2(+1 -1)
services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateProfile.java 28(+1 -27)
services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java 33(+1 -32)
services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java 28(+1 -27)
services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java 25(+12 -13)
Details
diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java
index efa9ed8..2ef01cb 100644
--- a/core/src/main/java/org/keycloak/OAuth2Constants.java
+++ b/core/src/main/java/org/keycloak/OAuth2Constants.java
@@ -116,6 +116,7 @@ public interface OAuth2Constants {
String UMA_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
+ String DISPLAY_CONSOLE = "console";
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
index 2f17f77..1d60052 100755
--- a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowError.java
@@ -43,5 +43,6 @@ public enum AuthenticationFlowError {
IDENTITY_PROVIDER_NOT_FOUND,
IDENTITY_PROVIDER_DISABLED,
- IDENTITY_PROVIDER_ERROR
+ IDENTITY_PROVIDER_ERROR,
+ DISPLAY_NOT_SUPPORTED
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
index bf8fbcf..e15386a 100755
--- a/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/AuthenticationFlowException.java
@@ -17,6 +17,8 @@
package org.keycloak.authentication;
+import javax.ws.rs.core.Response;
+
/**
* Throw this exception from an Authenticator, FormAuthenticator, or FormAction if you want to completely abort the flow.
*
@@ -25,11 +27,17 @@ package org.keycloak.authentication;
*/
public class AuthenticationFlowException extends RuntimeException {
private AuthenticationFlowError error;
+ private Response response;
public AuthenticationFlowException(AuthenticationFlowError error) {
this.error = error;
}
+ public AuthenticationFlowException(AuthenticationFlowError error, Response response) {
+ this.error = error;
+ this.response = response;
+ }
+
public AuthenticationFlowException(String message, AuthenticationFlowError error) {
super(message);
this.error = error;
@@ -53,4 +61,8 @@ public class AuthenticationFlowException extends RuntimeException {
public AuthenticationFlowError getError() {
return error;
}
+
+ public Response getResponse() {
+ return response;
+ }
}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeAuthenticatorFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeAuthenticatorFactory.java
new file mode 100644
index 0000000..0754383
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeAuthenticatorFactory.java
@@ -0,0 +1,21 @@
+package org.keycloak.authentication;
+
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * Implement this interface when declaring your authenticator factory
+ * if your provider has support for multiple oidc display query parameter parameter types
+ * if the display query parameter is set and your factory implements this interface, this method
+ * will be called.
+ *
+ */
+public interface DisplayTypeAuthenticatorFactory {
+ /**
+ *
+ *
+ * @param session
+ * @param displayType i.e. "console", "wap", "popup" are examples
+ * @return null if display type isn't support.
+ */
+ Authenticator createDisplay(KeycloakSession session, String displayType);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeRequiredActionFactory.java b/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeRequiredActionFactory.java
new file mode 100644
index 0000000..e22e9ff
--- /dev/null
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/DisplayTypeRequiredActionFactory.java
@@ -0,0 +1,13 @@
+package org.keycloak.authentication;
+
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * Implement this interface when declaring your required action factory
+ * has support for multiple oidc display query parameter parameter types
+ * if the display query parameter is set and your factory implements this interface, this method
+ * will be called.
+ */
+public interface DisplayTypeRequiredActionFactory {
+ RequiredActionProvider createDisplay(KeycloakSession session, String displayType);
+}
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java b/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java
index 46c0707..b1dc9a2 100644
--- a/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java
@@ -1,6 +1,7 @@
package org.keycloak.authentication;
import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.KeycloakSession;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
@@ -37,6 +38,20 @@ import javax.ws.rs.core.Response;
public class TextChallenge {
/**
+ * Browser is required to login. This will abort client from doing a console login.
+ *
+ * @param session
+ * @return
+ */
+ public static Response browserRequired(KeycloakSession session) {
+ return Response.status(Response.Status.UNAUTHORIZED)
+ .header("WWW-Authenticate", "X-Text-Form-Challenge browserRequired")
+ .type(MediaType.TEXT_PLAIN)
+ .entity("\n" + session.getProvider(LoginFormsProvider.class).getMessage("browserRequired") + "\n").build();
+ }
+
+
+ /**
* Build challenge response for required actions
*
* @param context
diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java
index 632c21c..95bcd81 100755
--- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java
+++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java
@@ -90,5 +90,6 @@ public interface Errors {
String NOT_LOGGED_IN = "not_logged_in";
String UNKNOWN_IDENTITY_PROVIDER = "unknown_identity_provider";
String ILLEGAL_ORIGIN = "illegal_origin";
+ String DISPLAY_UNSUPPORTED = "display_unsupported";
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 537581a..db96f11 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -653,27 +653,33 @@ public class AuthenticationProcessor {
public Response handleBrowserException(Exception failure) {
if (failure instanceof AuthenticationFlowException) {
AuthenticationFlowException e = (AuthenticationFlowException) failure;
+
if (e.getError() == AuthenticationFlowError.INVALID_USER) {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.USER_NOT_FOUND);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_USER);
} else if (e.getError() == AuthenticationFlowError.USER_DISABLED) {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.USER_DISABLED);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session,authenticationSession, Response.Status.BAD_REQUEST, Messages.ACCOUNT_DISABLED);
} else if (e.getError() == AuthenticationFlowError.USER_TEMPORARILY_DISABLED) {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.USER_TEMPORARILY_DISABLED);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session,authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_USER);
} else if (e.getError() == AuthenticationFlowError.INVALID_CLIENT_SESSION) {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.INVALID_CODE);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_CODE);
} else if (e.getError() == AuthenticationFlowError.EXPIRED_CODE) {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.EXPIRED_CODE);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.EXPIRED_CODE);
} else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
@@ -701,9 +707,15 @@ public class AuthenticationProcessor {
CacheControlUtil.noBackButtonCacheControlHeader();
return processor.authenticate();
+ } else if (e.getError() == AuthenticationFlowError.DISPLAY_NOT_SUPPORTED) {
+ ServicesLogger.LOGGER.failedAuthentication(e);
+ event.error(Errors.DISPLAY_UNSUPPORTED);
+ if (e.getResponse() != null) return e.getResponse();
+ return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.DISPLAY_UNSUPPORTED);
} else {
ServicesLogger.LOGGER.failedAuthentication(e);
event.error(Errors.INVALID_USER_CREDENTIALS);
+ if (e.getResponse() != null) return e.getResponse();
return ErrorPage.error(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_USER);
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AttemptedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AttemptedAuthenticator.java
new file mode 100644
index 0000000..fc866fd
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AttemptedAuthenticator.java
@@ -0,0 +1,46 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+/**
+ * Pass-thru atheneticator that just sets the context to attempted.
+ */
+public class AttemptedAuthenticator implements Authenticator {
+
+ public static final AttemptedAuthenticator SINGLETON = new AttemptedAuthenticator();
+ @Override
+ public void authenticate(AuthenticationFlowContext context) {
+ context.attempted();
+
+ }
+
+ @Override
+ public void action(AuthenticationFlowContext context) {
+ throw new RuntimeException("Unreachable!");
+
+ }
+
+ @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 void close() {
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
index 7e40298..b87dbe9 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticatorFactory.java
@@ -18,8 +18,11 @@
package org.keycloak.authentication.authenticators.browser;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -31,7 +34,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class CookieAuthenticatorFactory implements AuthenticatorFactory {
+public class CookieAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
public static final String PROVIDER_ID = "auth-cookie";
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
@@ -41,6 +44,13 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
}
@Override
+ public Authenticator createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return SINGLETON;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return AttemptedAuthenticator.SINGLETON; // ignore this authenticator
+ }
+
+ @Override
public void init(Config.Scope config) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java
index 635c95e..b136d33 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java
@@ -18,8 +18,11 @@
package org.keycloak.authentication.authenticators.browser;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.AttemptedAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -33,7 +36,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory {
+public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED
@@ -83,6 +86,13 @@ public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactor
}
@Override
+ public Authenticator createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return new IdentityProviderAuthenticator();
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return AttemptedAuthenticator.SINGLETON; // ignore this authenticator
+ }
+
+ @Override
public void init(Config.Scope config) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
index 955048b..9126689 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticator.java
@@ -20,8 +20,6 @@ package org.keycloak.authentication.authenticators.browser;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
-import org.keycloak.authentication.DisplayUtils;
-import org.keycloak.authentication.authenticators.console.ConsoleOTPFormAuthenticator;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
@@ -41,19 +39,11 @@ import javax.ws.rs.core.Response;
public class OTPFormAuthenticator extends AbstractUsernameFormAuthenticator implements Authenticator {
@Override
public void action(AuthenticationFlowContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleOTPFormAuthenticator.SINGLETON.action(context);
- return;
- }
validateOTP(context);
}
@Override
public void authenticate(AuthenticationFlowContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleOTPFormAuthenticator.SINGLETON.authenticate(context);
- return;
- }
Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse);
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
index f443d28..d71659c 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/OTPFormAuthenticatorFactory.java
@@ -18,8 +18,11 @@
package org.keycloak.authentication.authenticators.browser;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.console.ConsoleOTPFormAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -32,7 +35,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
+public class OTPFormAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
public static final String PROVIDER_ID = "auth-otp-form";
public static final OTPFormAuthenticator SINGLETON = new OTPFormAuthenticator();
@@ -43,6 +46,13 @@ public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
}
@Override
+ public Authenticator createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return SINGLETON;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleOTPFormAuthenticator.SINGLETON;
+ }
+
+ @Override
public void init(Config.Scope config) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
index 9d837c6..ae5dd0c 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SpnegoAuthenticatorFactory.java
@@ -18,8 +18,10 @@
package org.keycloak.authentication.authenticators.browser;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -32,7 +34,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
+public class SpnegoAuthenticatorFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
public static final String PROVIDER_ID = "auth-spnego";
public static final SpnegoAuthenticator SINGLETON = new SpnegoAuthenticator();
@@ -43,6 +45,13 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
}
@Override
+ public Authenticator createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return SINGLETON;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return SINGLETON;
+ }
+
+ @Override
public void init(Config.Scope config) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
index 1dc966f..43383a0 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java
@@ -20,8 +20,6 @@ package org.keycloak.authentication.authenticators.browser;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
-import org.keycloak.authentication.DisplayUtils;
-import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticator;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -42,10 +40,6 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
@Override
public void action(AuthenticationFlowContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUsernamePasswordAuthenticator.SINGLETON.action(context);
- return;
- }
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.cancelLogin();
@@ -63,10 +57,6 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
@Override
public void authenticate(AuthenticationFlowContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUsernamePasswordAuthenticator.SINGLETON.authenticate(context);
- return;
- }
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
String loginHint = context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
index ef0c9b1..fe42f48 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordFormFactory.java
@@ -18,8 +18,11 @@
package org.keycloak.authentication.authenticators.browser;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
+import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -32,7 +35,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UsernamePasswordFormFactory implements AuthenticatorFactory {
+public class UsernamePasswordFormFactory implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {
public static final String PROVIDER_ID = "auth-username-password-form";
public static final UsernamePasswordForm SINGLETON = new UsernamePasswordForm();
@@ -43,6 +46,13 @@ public class UsernamePasswordFormFactory implements AuthenticatorFactory {
}
@Override
+ public Authenticator createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return SINGLETON;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleUsernamePasswordAuthenticator.SINGLETON;
+ }
+
+ @Override
public void init(Config.Scope config) {
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleOTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleOTPFormAuthenticator.java
index 8fa34e9..fff2c80 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleOTPFormAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleOTPFormAuthenticator.java
@@ -19,13 +19,10 @@ package org.keycloak.authentication.authenticators.console;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
-import org.keycloak.authentication.DisplayUtils;
import org.keycloak.authentication.TextChallenge;
import org.keycloak.authentication.authenticators.browser.OTPFormAuthenticator;
import org.keycloak.representations.idm.CredentialRepresentation;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index 89471e7..ac0c5e1 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -18,6 +18,7 @@
package org.keycloak.authentication;
import org.jboss.logging.Logger;
+import org.keycloak.OAuth2Constants;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.UserModel;
@@ -58,6 +59,24 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
|| status == AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED;
}
+ protected Authenticator createAuthenticator(AuthenticatorFactory factory) {
+ String display = processor.getAuthenticationSession().getClientNote(OAuth2Constants.DISPLAY);
+ if (display == null) return factory.create(processor.getSession());
+
+
+ if (factory instanceof DisplayTypeAuthenticatorFactory) {
+ Authenticator authenticator = ((DisplayTypeAuthenticatorFactory)factory).createDisplay(processor.getSession(), display);
+ if (authenticator != null) return authenticator;
+ }
+ // todo create a provider for handling lack of display support
+ if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
+ throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, TextChallenge.browserRequired(processor.getSession()));
+
+ } else {
+ return factory.create(processor.getSession());
+ }
+ }
+
@Override
public Response processAction(String actionExecution) {
@@ -86,7 +105,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (factory == null) {
throw new RuntimeException("Unable to find factory for AuthenticatorFactory: " + model.getAuthenticator() + " did you forget to declare it in a META-INF/services file?");
}
- Authenticator authenticator = factory.create(processor.getSession());
+ Authenticator authenticator = createAuthenticator(factory);
AuthenticationProcessor.Result result = processor.createAuthenticatorContext(model, authenticator, executions);
logger.debugv("action: {0}", model.getAuthenticator());
authenticator.action(result);
@@ -161,7 +180,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (factory == null) {
throw new RuntimeException("Unable to find factory for AuthenticatorFactory: " + model.getAuthenticator() + " did you forget to declare it in a META-INF/services file?");
}
- Authenticator authenticator = factory.create(processor.getSession());
+ Authenticator authenticator = createAuthenticator(factory);
logger.debugv("authenticator: {0}", factory.getId());
UserModel authUser = processor.getAuthenticationSession().getAuthenticatedUser();
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java
index 41e07d9..24c6938 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java
@@ -33,31 +33,9 @@ import java.util.Arrays;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ConsoleTermsAndConditions implements RequiredActionProvider, RequiredActionFactory {
+public class ConsoleTermsAndConditions implements RequiredActionProvider {
public static final ConsoleTermsAndConditions SINGLETON = new ConsoleTermsAndConditions();
- public static final String PROVIDER_ID = "terms_and_conditions";
- public static final String USER_ATTRIBUTE = PROVIDER_ID;
-
- @Override
- public RequiredActionProvider create(KeycloakSession session) {
- return this;
- }
-
- @Override
- public void init(Config.Scope config) {
-
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
-
- }
-
- @Override
- public String getId() {
- return PROVIDER_ID;
- }
-
+ public static final String USER_ATTRIBUTE = TermsAndConditions.PROVIDER_ID;
@Override
public void evaluateTriggers(RequiredActionContext context) {
@@ -93,11 +71,6 @@ public class ConsoleTermsAndConditions implements RequiredActionProvider, Requir
}
@Override
- public String getDisplayText() {
- return "Terms and Conditions";
- }
-
- @Override
public void close() {
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java
index 5a9aaf9..d499ead 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java
@@ -38,7 +38,7 @@ import java.net.URI;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ConsoleUpdatePassword extends UpdatePassword implements RequiredActionProvider, RequiredActionFactory {
+public class ConsoleUpdatePassword extends UpdatePassword implements RequiredActionProvider {
public static final ConsoleUpdatePassword SINGLETON = new ConsoleUpdatePassword();
private static final Logger logger = Logger.getLogger(ConsoleUpdatePassword.class);
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateProfile.java
index a5fb335..0b66bef 100644
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateProfile.java
@@ -41,7 +41,7 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ConsoleUpdateProfile implements RequiredActionProvider, RequiredActionFactory {
+public class ConsoleUpdateProfile implements RequiredActionProvider {
public static final ConsoleUpdateProfile SINGLETON = new ConsoleUpdateProfile();
@Override
@@ -65,30 +65,4 @@ public class ConsoleUpdateProfile implements RequiredActionProvider, RequiredAct
public void close() {
}
-
- @Override
- public RequiredActionProvider create(KeycloakSession session) {
- return this;
- }
-
- @Override
- public void init(Config.Scope config) {
-
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
-
- }
-
- @Override
- public String getDisplayText() {
- return "Update Profile";
- }
-
-
- @Override
- public String getId() {
- return UserModel.RequiredAction.UPDATE_PROFILE.name();
- }
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java
index ec14bfa..89ef89b 100644
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java
@@ -44,7 +44,7 @@ import java.net.URI;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ConsoleUpdateTotp implements RequiredActionProvider, RequiredActionFactory {
+public class ConsoleUpdateTotp implements RequiredActionProvider {
public static final ConsoleUpdateTotp SINGLETON = new ConsoleUpdateTotp();
@Override
@@ -110,35 +110,4 @@ public class ConsoleUpdateTotp implements RequiredActionProvider, RequiredAction
public void close() {
}
-
- @Override
- public RequiredActionProvider create(KeycloakSession session) {
- return this;
- }
-
- @Override
- public void init(Config.Scope config) {
-
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
-
- }
-
- @Override
- public String getDisplayText() {
- return "Configure OTP";
- }
-
-
- @Override
- public String getId() {
- return UserModel.RequiredAction.CONFIGURE_TOTP.name();
- }
-
- @Override
- public boolean isOneTimeAction() {
- return true;
- }
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java
index 8eddf37..e136ceb 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java
@@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class ConsoleVerifyEmail implements RequiredActionProvider, RequiredActionFactory {
+public class ConsoleVerifyEmail implements RequiredActionProvider {
public static final ConsoleVerifyEmail SINGLETON = new ConsoleVerifyEmail();
private static final Logger logger = Logger.getLogger(ConsoleVerifyEmail.class);
@Override
@@ -113,33 +113,7 @@ public class ConsoleVerifyEmail implements RequiredActionProvider, RequiredActio
}
- @Override
- public RequiredActionProvider create(KeycloakSession session) {
- return this;
- }
-
- @Override
- public void init(Config.Scope config) {
-
- }
-
- @Override
- public void postInit(KeycloakSessionFactory factory) {
-
- }
-
- @Override
- public String getDisplayText() {
- return "Verify Email";
- }
-
-
public static String EMAIL_CODE="email_code";
- @Override
- public String getId() {
- return UserModel.RequiredAction.VERIFY_EMAIL.name();
- }
-
protected TextChallenge challenge(RequiredActionContext context) {
return TextChallenge.challenge(context)
.header()
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
index 961095c..2f68782 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java
@@ -18,10 +18,8 @@
package org.keycloak.authentication.requiredactions;
import org.keycloak.Config;
-import org.keycloak.authentication.DisplayUtils;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.*;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -33,7 +31,7 @@ import java.util.Arrays;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory {
+public class TermsAndConditions implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
public static final String PROVIDER_ID = "terms_and_conditions";
public static final String USER_ATTRIBUTE = PROVIDER_ID;
@@ -43,6 +41,15 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
}
@Override
+ public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return this;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleTermsAndConditions.SINGLETON;
+ }
+
+
+
+ @Override
public void init(Config.Scope config) {
}
@@ -66,20 +73,12 @@ public class TermsAndConditions implements RequiredActionProvider, RequiredActio
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleTermsAndConditions.SINGLETON.requiredActionChallenge(context);
- return;
- }
Response challenge = context.form().createForm("terms.ftl");
context.challenge(challenge);
}
@Override
public void processAction(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleTermsAndConditions.SINGLETON.processAction(context);
- return;
- }
if (context.getHttpRequest().getDecodedFormParameters().containsKey("cancel")) {
context.getUser().removeAttribute(USER_ATTRIBUTE);
context.failure();
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index 65ddc9e..45fb05f 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -19,10 +19,8 @@ package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger;
import org.keycloak.Config;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
-import org.keycloak.authentication.DisplayUtils;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.*;
import org.keycloak.common.util.Time;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.CredentialProvider;
@@ -48,7 +46,7 @@ import java.util.concurrent.TimeUnit;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory {
+public class UpdatePassword implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
private static final Logger logger = Logger.getLogger(UpdatePassword.class);
@Override
public void evaluateTriggers(RequiredActionContext context) {
@@ -75,10 +73,6 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdatePassword.SINGLETON.requiredActionChallenge(context);
- return;
- }
Response challenge = context.form()
.setAttribute("username", context.getAuthenticationSession().getAuthenticatedUser().getUsername())
.createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
@@ -87,10 +81,6 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
@Override
public void processAction(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdatePassword.SINGLETON.processAction(context);
- return;
- }
EventBuilder event = context.getEvent();
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
event.event(EventType.UPDATE_PASSWORD);
@@ -151,6 +141,15 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
return this;
}
+
+ @Override
+ public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return this;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleUpdatePassword.SINGLETON;
+ }
+
+
@Override
public void init(Config.Scope config) {
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
index f790626..ccaf729 100644
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java
@@ -18,10 +18,8 @@
package org.keycloak.authentication.requiredactions;
import org.keycloak.Config;
-import org.keycloak.authentication.DisplayUtils;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.*;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
@@ -42,17 +40,13 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory {
+public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdateProfile.SINGLETON.requiredActionChallenge(context);
- return;
- }
Response challenge = context.form()
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
@@ -60,10 +54,6 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
@Override
public void processAction(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdateProfile.SINGLETON.processAction(context);
- return;
- }
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
@@ -151,6 +141,16 @@ public class UpdateProfile implements RequiredActionProvider, RequiredActionFact
return this;
}
+
+ @Override
+ public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return this;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleUpdateProfile.SINGLETON;
+ }
+
+
+
@Override
public void init(Config.Scope config) {
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
index e715933..188ba2a 100644
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java
@@ -18,10 +18,8 @@
package org.keycloak.authentication.requiredactions;
import org.keycloak.Config;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
-import org.keycloak.authentication.DisplayUtils;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.*;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.KeycloakSession;
@@ -39,17 +37,13 @@ import javax.ws.rs.core.Response;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory {
+public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdateTotp.SINGLETON.requiredActionChallenge(context);
- return;
- }
Response challenge = context.form()
.setAttribute("mode", getMode(context))
.createResponse(UserModel.RequiredAction.CONFIGURE_TOTP);
@@ -62,10 +56,6 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
@Override
public void processAction(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleUpdateTotp.SINGLETON.processAction(context);
- return;
- }
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_TOTP);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
@@ -114,6 +104,15 @@ public class UpdateTotp implements RequiredActionProvider, RequiredActionFactory
return this;
}
+
+ @Override
+ public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return this;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleUpdateTotp.SINGLETON;
+ }
+
+
@Override
public void init(Config.Scope config) {
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index 9333905..c29c616 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -19,11 +19,9 @@ package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger;
import org.keycloak.Config;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.*;
import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
-import org.keycloak.authentication.DisplayUtils;
import org.keycloak.common.util.Time;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider;
@@ -46,7 +44,7 @@ import javax.ws.rs.core.*;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory {
+public class VerifyEmail implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
private static final Logger logger = Logger.getLogger(VerifyEmail.class);
@Override
public void evaluateTriggers(RequiredActionContext context) {
@@ -57,10 +55,6 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleVerifyEmail.SINGLETON.requiredActionChallenge(context);
- return;
- }
AuthenticationSessionModel authSession = context.getAuthenticationSession();
if (context.getUser().isEmailVerified()) {
@@ -93,10 +87,6 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
@Override
public void processAction(RequiredActionContext context) {
- if (DisplayUtils.isConsole(context)) {
- ConsoleVerifyEmail.SINGLETON.processAction(context);
- return;
- }
logger.debugf("Re-sending email requested for user: %s", context.getUser().getUsername());
// This will allow user to re-send email again
@@ -116,6 +106,14 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
return this;
}
+
+ @Override
+ public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
+ if (displayType == null) return this;
+ if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
+ return ConsoleVerifyEmail.SINGLETON;
+ }
+
@Override
public void init(Config.Scope config) {
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index a426bc2..edb9b51 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -21,11 +21,7 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier;
-import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionContextResult;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
+import org.keycloak.authentication.*;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.common.ClientConnection;
@@ -970,6 +966,25 @@ public class AuthenticationManager {
authSession.setProtocolMappers(requestedProtocolMappers);
}
+ public static RequiredActionProvider createRequiredAction(KeycloakSession session, RequiredActionFactory factory, AuthenticationSessionModel authSession) {
+ String display = authSession.getClientNote(OAuth2Constants.DISPLAY);
+ if (display == null) return factory.create(session);
+
+
+ if (factory instanceof DisplayTypeRequiredActionFactory) {
+ RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)factory).createDisplay(session, display);
+ if (provider != null) return provider;
+ }
+ // todo create a provider for handling lack of display support
+ if (OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(display)) {
+ throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, TextChallenge.browserRequired(session));
+
+ } else {
+ return factory.create(session);
+ }
+ }
+
+
protected static Response executionActions(KeycloakSession session, AuthenticationSessionModel authSession,
HttpRequest request, EventBuilder event, RealmModel realm, UserModel user,
Set<String> requiredActions) {
@@ -987,7 +1002,15 @@ public class AuthenticationManager {
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?");
}
- RequiredActionProvider actionProvider = factory.create(session);
+ RequiredActionProvider actionProvider = null;
+ try {
+ actionProvider = createRequiredAction(session, factory, authSession);
+ } catch (AuthenticationFlowException e) {
+ if (e.getResponse() != null) {
+ return e.getResponse();
+ }
+ throw e;
+ }
RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
actionProvider.requiredActionChallenge(context);
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index c6fb6c8..425a889 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -21,6 +21,7 @@ package org.keycloak.services.messages;
*/
public class Messages {
+ public static final String DISPLAY_UNSUPPORTED = "displayUnsupported";
public static final String LOGIN_TIMEOUT = "loginTimeout";
public static final String INVALID_USER = "invalidUserMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 7c38ba8..ef9525a 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -16,17 +16,12 @@
*/
package org.keycloak.services.resources;
+import org.keycloak.authentication.*;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
-import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.authentication.RequiredActionContext;
-import org.keycloak.authentication.RequiredActionContextResult;
-import org.keycloak.authentication.RequiredActionFactory;
-import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.TokenVerifier;
-import org.keycloak.authentication.ExplainedVerificationException;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
@@ -934,7 +929,15 @@ public class LoginActionsService {
event.error(Errors.INVALID_CODE);
throw new WebApplicationException(ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_CODE));
}
- RequiredActionProvider provider = factory.create(session);
+ RequiredActionProvider provider = null;
+ try {
+ provider = AuthenticationManager.createRequiredAction(session, factory, authSession);
+ } catch (AuthenticationFlowException e) {
+ if (e.getResponse() != null) {
+ return e.getResponse();
+ }
+ 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
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index b00140f..1e5d755 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -251,6 +251,7 @@
<packages>
<package>github.com/inconshreveable/mousetrap</package>
</packages>
+ <goPath>${project.build.directory}/gopath</goPath>
</configuration>
</execution>
<execution>
@@ -262,23 +263,11 @@
<packages>
<package>github.com/keycloak/kcinit</package>
</packages>
- <branch>0.1</branch>
+ <goPath>${project.build.directory}/gopath</goPath>
+ <tag>0.3</tag>
</configuration>
</execution>
- <execution>
- <id>build-kcinit</id>
- <goals>
- <goal>build</goal>
- </goals>
- <configuration>
- <resultName>kcinit</resultName>
- <packages>
- <package>github.com/keycloak/kcinit</package>
- </packages>
- <branch>0.1</branch>
- </configuration>
- </execution>
- </executions>
+ </executions>
</plugin>
</plugins>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java
index 0884d7a..f23a63c 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/KcinitTest.java
@@ -39,6 +39,7 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionManageme
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.forms.PassThroughAuthenticator;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
@@ -139,6 +140,23 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password("password"));
user.setEnabled(true);
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
+
+ // Parent flow
+ AuthenticationFlowModel browser = new AuthenticationFlowModel();
+ browser.setAlias("no-console-flow");
+ browser.setDescription("browser based authentication");
+ browser.setProviderId("basic-flow");
+ browser.setTopLevel(true);
+ browser.setBuiltIn(true);
+ browser = realm.addAuthenticationFlow(browser);
+
+ AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(browser.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ execution.setPriority(20);
+ execution.setAuthenticator(PassThroughAuthenticator.PROVIDER_ID);
+ realm.addAuthenticatorExecution(execution);
+
});
}
@@ -165,6 +183,40 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
}
@Test
+ public void testBrowserRequired() throws Exception {
+ // that that a browser require challenge is sent back if authentication flow doesn't support console display mode
+ testingClient.server().run(session -> {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
+ AuthenticationFlowModel flow = realm.getFlowByAlias("no-console-flow");
+ kcinit.setAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING, flow.getId());
+
+
+ });
+
+ testInstall();
+ // login
+ //System.out.println("login....");
+ KcinitExec exe = KcinitExec.newBuilder()
+ .argsLine("login")
+ .executeAsync();
+ exe.waitCompletion();
+ Assert.assertEquals(1, exe.exitCode());
+ Assert.assertTrue(exe.stderrString().contains("Browser required to login"));
+ //Assert.assertEquals("stderr first line", "Browser required to login", exe.stderrLines().get(1));
+
+
+ testingClient.server().run(session -> {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
+ kcinit.removeAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING);
+
+
+ });
+ }
+
+
+ @Test
public void testBadCommand() throws Exception {
KcinitExec exe = KcinitExec.execute("covfefe");
Assert.assertEquals(1, exe.exitCode());
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 3b1eded..5f4c6a2 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -34,6 +34,8 @@ emailForgotTitle=Forgot Your Password?
updatePasswordTitle=Update password
codeSuccessTitle=Success code
codeErrorTitle=Error code\: {0}
+displayUnsupported=Requested display type unsupported
+browserRequired=Browser required to login
termsTitle=Terms and Conditions
termsText=<p>Terms and conditions to be defined</p>