Merge pull request #5115 from patriot1burke/kcinit-browser
KEYCLOAK-7004 KEYCLOAK-7003 KEYCLOAK-6999 KEYCLOAK-7033
This commit is contained in:
commit
0b2fe75828
24 changed files with 352 additions and 201 deletions
|
@ -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("<html><h1>Login completed.</h1><div>");
|
||||
pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
|
||||
pw.println("</div></html>");
|
||||
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("<html><h1>Login attempt failed.</h1><div>");
|
||||
pw.println("</div></html>");
|
||||
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("<html><h1>Logout completed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
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("<html><h1>Logout failed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
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) {
|
||||
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 {
|
||||
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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -45,8 +40,8 @@ public class ConsoleUpdatePassword extends UpdatePassword implements RequiredAct
|
|||
public static final String PASSWORD_NEW = "password-new";
|
||||
public static final String PASSWORD_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")
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -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")
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -114,8 +103,8 @@ public class ConsoleVerifyEmail implements RequiredActionProvider {
|
|||
}
|
||||
|
||||
public static String EMAIL_CODE="email_code";
|
||||
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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -264,7 +264,7 @@
|
|||
<package>github.com/keycloak/kcinit</package>
|
||||
</packages>
|
||||
<goPath>${project.build.directory}/gopath</goPath>
|
||||
<tag>0.3</tag>
|
||||
<tag>0.4</tag>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,18 +224,57 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
|
||||
});
|
||||
//Thread.sleep(100000000);
|
||||
|
||||
try {
|
||||
|
||||
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));
|
||||
|
||||
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");
|
||||
|
@ -214,6 +284,45 @@ public class KcinitTest extends AbstractTestRealmKeycloakTest {
|
|||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@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 -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(0, exe.exitCode());
|
||||
Assert.assertTrue(driver.getPageSource().contains("Login Successful"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
|
@ -243,13 +352,35 @@ 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();
|
||||
|
@ -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"));
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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">
|
||||
<div id="kc-info-message">
|
||||
<p class="instruction">${message.summary}<#if requiredActions??><#list requiredActions>: <b><#items as reqActionItem>${msg("requiredAction.${reqActionItem}")}<#sep>, </#items></b></#list><#else></#if></p>
|
||||
|
|
|
@ -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=<p>Terms and conditions to be defined</p>
|
||||
|
@ -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}.
|
||||
|
|
Loading…
Reference in a new issue