diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index 4f311c223f..501166722b 100644
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -98,70 +98,12 @@ public class KeycloakInstalled {
this.deployment = deployment;
}
- private static HttpResponseWriter defaultLoginWriter = new HttpResponseWriter() {
- @Override
- public void success(PrintWriter pw, KeycloakInstalled ki) {
- pw.println("HTTP/1.1 200 OK");
- pw.println("Content-Type: text/html");
- pw.println();
- pw.println("
Login completed.
");
- pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
- pw.println("
");
- pw.flush();
-
- }
-
- @Override
- public void failure(PrintWriter pw, KeycloakInstalled ki) {
- pw.println("HTTP/1.1 200 OK");
- pw.println("Content-Type: text/html");
- pw.println();
- pw.println("Login attempt failed.
");
- pw.println("
");
- pw.flush();
-
- }
- };
- private static HttpResponseWriter defaultLogoutWriter = new HttpResponseWriter() {
- @Override
- public void success(PrintWriter pw, KeycloakInstalled ki) {
- pw.println("HTTP/1.1 200 OK");
- pw.println("Content-Type: text/html");
- pw.println();
- pw.println("Logout completed.
");
- pw.println("You may close this browser tab.");
- pw.println("
");
- pw.flush();
-
- }
-
- @Override
- public void failure(PrintWriter pw, KeycloakInstalled ki) {
- pw.println("HTTP/1.1 200 OK");
- pw.println("Content-Type: text/html");
- pw.println();
- pw.println("Logout failed.
");
- pw.println("You may close this browser tab.");
- pw.println("
");
- pw.flush();
-
- }
- };
-
public HttpResponseWriter getLoginResponseWriter() {
- if (loginResponseWriter == null) {
- return defaultLoginWriter;
- } else {
- return loginResponseWriter;
- }
+ return null;
}
public HttpResponseWriter getLogoutResponseWriter() {
- if (logoutResponseWriter == null) {
- return defaultLogoutWriter;
- } else {
- return logoutResponseWriter;
- }
+ return null;
}
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
@@ -709,11 +651,26 @@ public class KeycloakInstalled {
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
PrintWriter pw = new PrintWriter(out);
+ if (writer != null) {
+ System.err.println("Using a writer is deprecated. Please remove its usage. This is now handled by endpoint on server");
+ }
if (error == null) {
- writer.success(pw, KeycloakInstalled.this);
+ if (writer != null) {
+ writer.success(pw, KeycloakInstalled.this);
+ } else {
+ pw.println("HTTP/1.1 302 Found");
+ pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated"));
+
+ }
} else {
- writer.failure(pw, KeycloakInstalled.this);
+ if (writer != null) {
+ writer.failure(pw, KeycloakInstalled.this);
+ } else {
+ pw.println("HTTP/1.1 302 Found");
+ pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated?error=true"));
+
+ }
}
pw.flush();
socket.close();
diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java b/server-spi-private/src/main/java/org/keycloak/authentication/ConsoleDisplayMode.java
similarity index 82%
rename from server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java
rename to server-spi-private/src/main/java/org/keycloak/authentication/ConsoleDisplayMode.java
index b1dc9a2d15..f0f31872c0 100644
--- a/server-spi-private/src/main/java/org/keycloak/authentication/TextChallenge.java
+++ b/server-spi-private/src/main/java/org/keycloak/authentication/ConsoleDisplayMode.java
@@ -35,7 +35,7 @@ import javax.ws.rs.core.Response;
* 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.
@@ -50,6 +50,27 @@ public class TextChallenge {
.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
@@ -57,8 +78,8 @@ public class TextChallenge {
* @param context
* @return
*/
- public static TextChallenge challenge(RequiredActionContext context) {
- return new TextChallenge(context);
+ public static ConsoleDisplayMode challenge(RequiredActionContext context) {
+ return new ConsoleDisplayMode(context);
}
@@ -68,8 +89,8 @@ public class TextChallenge {
* @param context
* @return
*/
- public static TextChallenge challenge(AuthenticationFlowContext context) {
- return new TextChallenge(context);
+ public static ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
+ return new ConsoleDisplayMode(context);
}
/**
@@ -79,7 +100,7 @@ public class TextChallenge {
* @return
*/
public static HeaderBuilder header(RequiredActionContext context) {
- return new TextChallenge(context).header();
+ return new ConsoleDisplayMode(context).header();
}
@@ -90,14 +111,14 @@ public class TextChallenge {
* @return
*/
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;
}
- TextChallenge(AuthenticationFlowContext flowContext) {
+ ConsoleDisplayMode(AuthenticationFlowContext flowContext) {
this.flowContext = flowContext;
}
@@ -278,20 +299,20 @@ public class TextChallenge {
return HeaderBuilder.this.build();
}
- public TextChallenge challenge() {
- return TextChallenge.this;
+ public ConsoleDisplayMode challenge() {
+ return ConsoleDisplayMode.this;
}
public LoginFormsProvider form() {
- return TextChallenge.this.form();
+ return ConsoleDisplayMode.this.form();
}
public Response message(String msg, String... params) {
- return TextChallenge.this.message(msg, params);
+ return ConsoleDisplayMode.this.message(msg, params);
}
public Response text(String text) {
- return TextChallenge.this.text(text);
+ return ConsoleDisplayMode.this.text(text);
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index db96f11e68..502ac6d9e2 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -254,6 +254,19 @@ public class AuthenticationProcessor {
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 {
AuthenticatorConfigModel authenticatorConfig;
AuthenticationExecutionModel execution;
@@ -546,15 +559,7 @@ public class AuthenticationProcessor {
@Override
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());
+ return AuthenticationProcessor.this.getRefreshUrl(authSessionIdParam);
}
@Override
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index ac0c5e1a4b..3c4c2e639e 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -60,7 +60,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
}
protected Authenticator createAuthenticator(AuthenticatorFactory factory) {
- String display = processor.getAuthenticationSession().getClientNote(OAuth2Constants.DISPLAY);
+ String display = processor.getAuthenticationSession().getAuthNote(OAuth2Constants.DISPLAY);
if (display == null) return factory.create(processor.getSession());
@@ -70,7 +70,9 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
}
// 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()));
+ processor.getAuthenticationSession().removeAuthNote(OAuth2Constants.DISPLAY);
+ throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED,
+ ConsoleDisplayMode.browserContinue(processor.getSession(), processor.getRefreshUrl(true).toString()));
} else {
return factory.create(processor.getSession());
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index 38b9c2fe0e..f79323443a 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -65,6 +65,10 @@ public class RequiredActionContextResult implements RequiredActionContext {
this.factory = factory;
}
+ public RequiredActionFactory getFactory() {
+ return factory;
+ }
+
@Override
public EventBuilder getEvent() {
return eventBuilder;
@@ -170,7 +174,6 @@ public class RequiredActionContextResult implements RequiredActionContext {
uri = UriBuilder.fromUri(uri).queryParam(LoginActionsService.AUTH_SESSION_ID, getAuthenticationSession().getParentSession().getId()).build();
}
return uri;
-
}
@Override
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 fff2c80e75..0335b17436 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,7 +19,7 @@ package org.keycloak.authentication.authenticators.console;
import org.keycloak.authentication.AuthenticationFlowContext;
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.representations.idm.CredentialRepresentation;
@@ -37,8 +37,8 @@ public class ConsoleOTPFormAuthenticator extends OTPFormAuthenticator implements
return context.getActionUrl(context.generateAccessCode(), true);
}
- protected TextChallenge challenge(AuthenticationFlowContext context) {
- return TextChallenge.challenge(context)
+ protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
+ return ConsoleDisplayMode.challenge(context)
.header()
.param(CredentialRepresentation.TOTP)
.label("console-otp")
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleUsernamePasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleUsernamePasswordAuthenticator.java
index 4595df58f1..720e4e59fd 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleUsernamePasswordAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/console/ConsoleUsernamePasswordAuthenticator.java
@@ -24,11 +24,8 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
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.Response;
-import java.net.URI;
/**
* @author Bill Burke
@@ -43,8 +40,8 @@ public class ConsoleUsernamePasswordAuthenticator extends AbstractUsernameFormAu
return false;
}
- protected TextChallenge challenge(AuthenticationFlowContext context) {
- return TextChallenge.challenge(context)
+ protected ConsoleDisplayMode challenge(AuthenticationFlowContext context) {
+ return ConsoleDisplayMode.challenge(context)
.header()
.param("username")
.label("console-username")
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 24c6938771..1db910dbe9 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleTermsAndConditions.java
@@ -17,14 +17,10 @@
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.TextChallenge;
+import org.keycloak.authentication.ConsoleDisplayMode;
import org.keycloak.common.util.Time;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
import javax.ws.rs.core.Response;
import java.util.Arrays;
@@ -45,7 +41,7 @@ public class ConsoleTermsAndConditions implements RequiredActionProvider {
@Override
public void requiredActionChallenge(RequiredActionContext context) {
- Response challenge = TextChallenge.challenge(context)
+ Response challenge = ConsoleDisplayMode.challenge(context)
.header()
.param("accept")
.label("console-accept-terms")
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 d499eadaaf..461007ab04 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdatePassword.java
@@ -18,7 +18,6 @@
package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger;
-import org.keycloak.Config;
import org.keycloak.authentication.*;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -28,11 +27,7 @@ import org.keycloak.models.*;
import org.keycloak.services.messages.Messages;
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.Response;
-import java.net.URI;
/**
* @author Bill Burke
@@ -45,8 +40,8 @@ public class ConsoleUpdatePassword extends UpdatePassword implements RequiredAct
public static final String PASSWORD_NEW = "password-new";
public static final String PASSWORD_CONFIRM = "password-confirm";
- protected TextChallenge challenge(RequiredActionContext context) {
- return TextChallenge.challenge(context)
+ protected ConsoleDisplayMode challenge(RequiredActionContext context) {
+ return ConsoleDisplayMode.challenge(context)
.header()
.param(PASSWORD_NEW)
.label("console-new-password")
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 89ef89b6b1..32751b99e4 100644
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleUpdateTotp.java
@@ -17,28 +17,19 @@
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.TextChallenge;
+import org.keycloak.authentication.ConsoleDisplayMode;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.forms.login.LoginFormsProvider;
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.UserModel;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.services.messages.Messages;
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.Response;
-import java.net.URI;
/**
* @author Bill Burke
@@ -61,8 +52,8 @@ public class ConsoleUpdateTotp implements RequiredActionProvider {
context.challenge(challenge);
}
- protected TextChallenge challenge(RequiredActionContext context) {
- return TextChallenge.challenge(context)
+ protected ConsoleDisplayMode challenge(RequiredActionContext context) {
+ return ConsoleDisplayMode.challenge(context)
.header()
.param("totp")
.label("console-otp")
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 e136cebf67..e3c6ec5a30 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/ConsoleVerifyEmail.java
@@ -18,35 +18,24 @@
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.TextChallenge;
-import org.keycloak.authentication.actiontoken.verifyemail.VerifyEmailActionToken;
+import org.keycloak.authentication.ConsoleDisplayMode;
import org.keycloak.common.util.RandomString;
-import org.keycloak.common.util.Time;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.*;
-import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.validation.Validation;
-import org.keycloak.sessions.AuthenticationSessionCompoundId;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.*;
-import java.net.URI;
-import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.TimeUnit;
/**
* @author Bill Burke
@@ -114,8 +103,8 @@ public class ConsoleVerifyEmail implements RequiredActionProvider {
}
public static String EMAIL_CODE="email_code";
- protected TextChallenge challenge(RequiredActionContext context) {
- return TextChallenge.challenge(context)
+ protected ConsoleDisplayMode challenge(RequiredActionContext context) {
+ return ConsoleDisplayMode.challenge(context)
.header()
.param(EMAIL_CODE)
.label("console-email-code")
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 56c002280d..148d840ace 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -71,7 +71,6 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String MAX_AGE_PARAM = OAuth2Constants.MAX_AGE;
public static final String PROMPT_PARAM = OAuth2Constants.PROMPT;
public static final String LOGIN_HINT_PARAM = "login_hint";
- public static final String DISPLAY_PARAM = "display";
public static final String REQUEST_PARAM = "request";
public static final String REQUEST_URI_PARAM = "request_uri";
public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index 160013fefb..6fa6705c85 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -20,6 +20,7 @@ package org.keycloak.protocol.oidc;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.common.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.jose.jwk.JSONWebKeySet;
@@ -27,6 +28,7 @@ import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.keys.KeyMetadata;
import org.keycloak.keys.RsaKeyMetadata;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint;
@@ -34,6 +36,8 @@ import org.keycloak.protocol.oidc.endpoints.LoginStatusIframeEndpoint;
import org.keycloak.protocol.oidc.endpoints.LogoutEndpoint;
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
import org.keycloak.protocol.oidc.endpoints.UserInfoEndpoint;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CacheControlUtil;
@@ -75,6 +79,9 @@ public class OIDCLoginProtocolService {
@Context
private HttpRequest request;
+ @Context
+ private ClientConnection clientConnection;
+
public OIDCLoginProtocolService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.tokenManager = new TokenManager();
@@ -228,4 +235,31 @@ public class OIDCLoginProtocolService {
}
}
+ /**
+ * For KeycloakInstalled and kcinit login where command line login is delegated to a browser.
+ * This clears login cookies and outputs login success or failure messages.
+ *
+ * @param error
+ * @return
+ */
+ @GET
+ @Path("delegated")
+ public Response kcinitBrowserLoginComplete(@QueryParam("error") boolean error) {
+ AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
+ AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
+ if (error) {
+ LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class);
+ return forms
+ .setAttribute("messageHeader", forms.getMessage(Messages.DELEGATION_FAILED_HEADER))
+ .setAttribute(Constants.SKIP_LINK, true).setError(Messages.DELEGATION_FAILED).createInfoPage();
+
+ } else {
+ LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class);
+ return forms
+ .setAttribute("messageHeader", forms.getMessage(Messages.DELEGATION_COMPLETE_HEADER))
+ .setAttribute(Constants.SKIP_LINK, true)
+ .setSuccess(Messages.DELEGATION_COMPLETE).createInfoPage();
+ }
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 65c66e28e5..666cf3e2e2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -371,7 +371,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims());
if (request.getAcr() != null) authenticationSession.setClientNote(OIDCLoginProtocol.ACR_PARAM, request.getAcr());
- if (request.getDisplay() != null) authenticationSession.setClientNote(OAuth2Constants.DISPLAY, request.getDisplay());
+ if (request.getDisplay() != null) authenticationSession.setAuthNote(OAuth2Constants.DISPLAY, request.getDisplay());
// https://tools.ietf.org/html/rfc7636#section-4
if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
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 edb9b511bb..87df91759f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -966,21 +966,22 @@ 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);
+ public static RequiredActionProvider createRequiredAction(RequiredActionContextResult context) {
+ String display = context.getAuthenticationSession().getAuthNote(OAuth2Constants.DISPLAY);
+ if (display == null) return context.getFactory().create(context.getSession());
- if (factory instanceof DisplayTypeRequiredActionFactory) {
- RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)factory).createDisplay(session, display);
+ if (context.getFactory() instanceof DisplayTypeRequiredActionFactory) {
+ RequiredActionProvider provider = ((DisplayTypeRequiredActionFactory)context.getFactory()).createDisplay(context.getSession(), 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));
+ context.getAuthenticationSession().removeAuthNote(OAuth2Constants.DISPLAY);
+ throw new AuthenticationFlowException(AuthenticationFlowError.DISPLAY_NOT_SUPPORTED, ConsoleDisplayMode.browserContinue(context.getSession(), context.getUriInfo().getRequestUri().toString()));
} else {
- return factory.create(session);
+ return context.getFactory().create(context.getSession());
}
}
@@ -1002,16 +1003,16 @@ 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?");
}
+ RequiredActionContextResult context = new RequiredActionContextResult(authSession, realm, event, session, request, user, factory);
RequiredActionProvider actionProvider = null;
try {
- actionProvider = createRequiredAction(session, factory, authSession);
+ actionProvider = createRequiredAction(context);
} 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);
if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
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 425a88927f..5a825ccc51 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -225,4 +225,9 @@ public class Messages {
public static final String INTERNAL_SERVER_ERROR = "internalServerError";
+ public static final String DELEGATION_COMPLETE = "delegationCompleteMessage";
+ public static final String DELEGATION_COMPLETE_HEADER = "delegationCompleteHeader";
+ public static final String DELEGATION_FAILED = "delegationFailedMessage";
+ public static final String DELEGATION_FAILED_HEADER = "delegationFailedHeader";
+
}
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 ef9525a96c..f517c7d162 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -929,9 +929,15 @@ public class LoginActionsService {
event.error(Errors.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;
try {
- provider = AuthenticationManager.createRequiredAction(session, factory, authSession);
+ provider = AuthenticationManager.createRequiredAction(context);
} catch (AuthenticationFlowException e) {
if (e.getResponse() != null) {
return e.getResponse();
@@ -939,12 +945,6 @@ public class LoginActionsService {
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;
provider.processAction(context);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 09892cbf0f..f689f6e15c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -306,14 +306,7 @@ public class AuthenticationManagementResource {
logger.debug("flow not found: " + flowAlias);
return Response.status(NOT_FOUND).build();
}
- 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(newName, flow, copy);
+ AuthenticationFlowModel copy = copyFlow(realm, flow, newName);
data.put("id", copy.getId());
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())) {
if (execution.isAuthenticatorFlow()) {
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
@@ -334,7 +339,7 @@ public class AuthenticationManagementResource {
copy.setTopLevel(false);
copy = realm.addAuthenticationFlow(copy);
execution.setFlowId(copy.getId());
- copy(newName, subFlow, copy);
+ copy(realm, newName, subFlow, copy);
}
execution.setId(null);
execution.setParentFlow(to.getId());
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/actions/DummyRequiredActionFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/actions/DummyRequiredActionFactory.java
index fb2f74d418..600d6f7470 100755
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/actions/DummyRequiredActionFactory.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/actions/DummyRequiredActionFactory.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.actions;
import org.keycloak.Config;
+import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.models.KeycloakSession;
@@ -38,7 +39,27 @@ public class DummyRequiredActionFactory implements RequiredActionFactory {
@Override
public RequiredActionProvider create(KeycloakSession session) {
- return null;
+ return new RequiredActionProvider() {
+ @Override
+ public void evaluateTriggers(RequiredActionContext context) {
+
+ }
+
+ @Override
+ public void requiredActionChallenge(RequiredActionContext context) {
+ context.success();
+ }
+
+ @Override
+ public void processAction(RequiredActionContext context) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ };
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 1e5d7552d4..754c6dad32 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -264,7 +264,7 @@
github.com/keycloak/kcinit
${project.build.directory}/gopath
- 0.3
+ 0.4
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java
index ddfe91dd73..e7cd34de86 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java
@@ -239,7 +239,7 @@ public abstract class AbstractExec {
}
}
- throw new RuntimeException("Timed while waiting for content to appear in stdout");
+ throw new RuntimeException("Timed while waiting for content to appear in stderr");
}
public void sendToStdin(String s) {
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 f23a63c4b2..2d5ca5b7fa 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
@@ -25,20 +25,26 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticatorFactory;
import org.keycloak.authentication.requiredactions.TermsAndConditions;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.*;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
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.AdminPermissions;
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.actions.DummyRequiredActionFactory;
+import org.keycloak.testsuite.authentication.PushButtonAuthenticator;
+import org.keycloak.testsuite.authentication.PushButtonAuthenticatorFactory;
import org.keycloak.testsuite.forms.PassThroughAuthenticator;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
@@ -49,8 +55,11 @@ import org.keycloak.testsuite.util.MailUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.TotpUtils;
+import org.openqa.selenium.By;
import javax.mail.internet.MimeMessage;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@@ -69,6 +78,9 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
@Rule
public AssertEvents events = new AssertEvents(this);
+ @Page
+ protected LoginPage loginPage;
+
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
}
@@ -95,10 +107,9 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
}
ClientModel kcinit = realm.addClient(KCINIT_CLIENT);
- kcinit.setSecret("password");
kcinit.setEnabled(true);
- kcinit.addRedirectUri("urn:ietf:wg:oauth:2.0:oob");
- kcinit.setPublicClient(false);
+ kcinit.addRedirectUri("http://localhost:*");
+ kcinit.setPublicClient(true);
ClientModel app = realm.addClient(APP);
app.setSecret("password");
@@ -154,9 +165,29 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
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);
+
+ RequiredActionProviderModel action = new RequiredActionProviderModel();
+ action.setAlias("dummy");
+ action.setEnabled(true);
+ action.setProviderId(DummyRequiredActionFactory.PROVIDER_ID);
+ action.setName("dummy");
+ action = realm.addRequiredActionProvider(action);
+
+
});
}
@@ -183,8 +214,8 @@ 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
+ public void testBrowserContinueAuthenticator() throws Exception {
+ // test that we can continue in the middle of a console login that doesn't support console display mode
testingClient.server().run(session -> {
RealmModel realm = session.realms().getRealmByName("test");
ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
@@ -193,29 +224,107 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
});
+ //Thread.sleep(100000000);
+ try {
+
+ testInstall();
+
+ KcinitExec exe = KcinitExec.newBuilder()
+ .argsLine("login -f --fake-browser") // --fake-browser is a hidden command so that this test can execute
+ .executeAsync();
+ exe.waitForStderr("Open browser and continue login? [y/n]");
+ exe.sendLine("y");
+ exe.waitForStdout("http://");
+
+ // the --fake-browser skips launching a browser and outputs url to stdout
+ String redirect = exe.stdoutString().trim();
+
+ //System.out.println("********************************");
+ //System.out.println("Redirect: " + redirect);
+
+ //redirect.replace("Browser required to complete login", "");
+
+ driver.navigate().to(redirect.trim());
+
+ Assert.assertEquals("PushTheButton", driver.getTitle());
+
+ // Push the button. I am redirected to username+password form
+ driver.findElement(By.name("submit1")).click();
+ //System.out.println("-----");
+ //System.out.println(driver.getPageSource());
+
+ //System.out.println(driver.getTitle());
+
+
+
+ loginPage.assertCurrent();
+
+ // Fill username+password. I am successfully authenticated
+ try {
+ oauth.fillLoginForm("wburke", "password");
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+
+
+ String current = driver.getCurrentUrl();
+
+ exe.waitForStderr("Login successful");
+ exe.waitCompletion();
+ Assert.assertEquals(0, exe.exitCode());
+ Assert.assertTrue(driver.getPageSource().contains("Login Successful"));
+ } finally {
+
+ testingClient.server().run(session -> {
+ RealmModel realm = session.realms().getRealmByName("test");
+ ClientModel kcinit = realm.getClientByClientId(KCINIT_CLIENT);
+ kcinit.removeAuthenticationFlowBindingOverride(AuthenticationFlowBindings.BROWSER_BINDING);
+
+
+ });
+ }
+ }
+
+ @Test
+ public void testBrowserContinueRequiredAction() throws Exception {
+ testingClient.server().run(session -> {
+ RealmModel realm = session.realms().getRealmByName("test");
+ UserModel user = session.users().getUserByUsername("wburke", realm);
+ user.addRequiredAction("dummy");
+ });
testInstall();
// login
//System.out.println("login....");
KcinitExec exe = KcinitExec.newBuilder()
- .argsLine("login")
+ .argsLine("login -f --fake-browser")
.executeAsync();
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Username:");
+ exe.sendLine("wburke");
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Password:");
+ exe.sendLine("password");
+
+ exe.waitForStderr("Open browser and continue login? [y/n]");
+ exe.sendLine("y");
+ exe.waitForStdout("http://");
+
+ // the --fake-browser skips launching a browser and outputs url to stdout
+ String redirect = exe.stdoutString().trim();
+
+ driver.navigate().to(redirect.trim());
+
+
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Login successful");
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);
-
-
- });
+ Assert.assertEquals(0, exe.exitCode());
+ Assert.assertTrue(driver.getPageSource().contains("Login Successful"));
}
+
@Test
public void testBadCommand() throws Exception {
KcinitExec exe = KcinitExec.execute("covfefe");
@@ -243,14 +352,36 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
exe.waitForStderr("client id [kcinit]:");
exe.sendLine("");
//System.out.println(exe.stderrString());
- exe.waitForStderr("Client secret [none]:");
- exe.sendLine("password");
+ exe.waitForStderr("secret [none]:");
+ exe.sendLine("");
//System.out.println(exe.stderrString());
exe.waitCompletion();
Assert.assertEquals(0, exe.exitCode());
}
@Test
+ public void testOffline() throws Exception {
+ testInstall();
+ // login
+ //System.out.println("login....");
+ KcinitExec exe = KcinitExec.newBuilder()
+ .argsLine("login --offline")
+ .executeAsync();
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Username:");
+ exe.sendLine("wburke");
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Password:");
+ exe.sendLine("password");
+ //System.out.println(exe.stderrString());
+ exe.waitForStderr("Offline tokens not allowed for the user or client");
+ exe.waitCompletion();
+ Assert.assertEquals(1, exe.exitCode());
+ }
+
+
+
+ @Test
public void testBasic() throws Exception {
testInstall();
// login
@@ -275,12 +406,6 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
Assert.assertEquals(1, exe.stdoutLines().size());
String token = exe.stdoutLines().get(0).trim();
//System.out.println("token: " + token);
- String introspect = oauth.introspectAccessTokenWithClientCredential("kcinit", "password", token);
- Map json = JsonSerialization.readValue(introspect, Map.class);
- Assert.assertTrue(json.containsKey("active"));
- Assert.assertTrue((Boolean)json.get("active"));
- //System.out.println("introspect");
- //System.out.println(introspect);
exe = KcinitExec.execute("token app");
Assert.assertEquals(0, exe.exitCode());
@@ -288,10 +413,6 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
String appToken = exe.stdoutLines().get(0).trim();
Assert.assertFalse(appToken.equals(token));
//System.out.println("token: " + token);
- introspect = oauth.introspectAccessTokenWithClientCredential("kcinit", "password", appToken);
- json = JsonSerialization.readValue(introspect, Map.class);
- Assert.assertTrue(json.containsKey("active"));
- Assert.assertTrue((Boolean)json.get("active"));
exe = KcinitExec.execute("token badapp");
@@ -303,10 +424,6 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
exe = KcinitExec.execute("logout");
Assert.assertEquals(0, exe.exitCode());
- introspect = oauth.introspectAccessTokenWithClientCredential("kcinit", "password", token);
- json = JsonSerialization.readValue(introspect, Map.class);
- Assert.assertTrue(json.containsKey("active"));
- Assert.assertFalse((Boolean)json.get("active"));
diff --git a/themes/src/main/resources/theme/base/login/info.ftl b/themes/src/main/resources/theme/base/login/info.ftl
index ab8c567ff6..8eff9c3622 100755
--- a/themes/src/main/resources/theme/base/login/info.ftl
+++ b/themes/src/main/resources/theme/base/login/info.ftl
@@ -1,7 +1,11 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayMessage=false; section>
<#if section = "header">
+ <#if messageHeader??>
+ ${messageHeader}
+ <#else>
${message.summary}
+ #if>
<#elseif section = "form">
${message.summary}<#if requiredActions??><#list requiredActions>: <#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, #items>#list><#else>#if>
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 5f4c6a2f6d..253a20bcd5 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
@@ -36,6 +36,10 @@ codeSuccessTitle=Success code
codeErrorTitle=Error code\: {0}
displayUnsupported=Requested display type unsupported
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
termsText=
Terms and conditions to be defined
@@ -186,6 +190,11 @@ emailSendErrorMessage=Failed to send email, please try again later.
accountUpdatedMessage=Your account has been updated.
accountPasswordUpdatedMessage=Your password has been updated.
+delegationCompleteHeader=Login Successful
+delegationCompleteMessage=You may close this browser window and go back to your console application.
+delegationFailedHeader=Login Failed
+delegationFailedMessage=You may close this browser window and go back to your console application and try logging in again.
+
noAccessMessage=No access
invalidPasswordMinLengthMessage=Invalid password: minimum length {0}.