KEYCLOAK-2709 SAML Identity Provider POST Binding request page shown to user is comletely blank with nonsense title
This commit is contained in:
parent
38933fdfed
commit
d64f716a20
16 changed files with 191 additions and 32 deletions
|
@ -24,7 +24,9 @@ import java.util.Map;
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
* @deprecated Class is deprecated and may be removed in the future. Use org.keycloak.saml.BaseSAML2BindingBuilder#buildHtml instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class HttpPostRedirect {
|
public class HttpPostRedirect {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -92,6 +92,8 @@ public interface GeneralConstants {
|
||||||
|
|
||||||
String ROLE_VALIDATOR_IGNORE = "ROLE_VALIDATOR_IGNORE";
|
String ROLE_VALIDATOR_IGNORE = "ROLE_VALIDATOR_IGNORE";
|
||||||
|
|
||||||
|
String URL = "url";
|
||||||
|
|
||||||
String SAML_REQUEST_KEY = "SAMLRequest";
|
String SAML_REQUEST_KEY = "SAMLRequest";
|
||||||
|
|
||||||
String SAML_RESPONSE_KEY = "SAMLResponse";
|
String SAML_RESPONSE_KEY = "SAMLResponse";
|
||||||
|
|
|
@ -139,7 +139,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BasePostBindingBuilder {
|
public class BasePostBindingBuilder {
|
||||||
protected Document document;
|
protected Document document;
|
||||||
protected BaseSAML2BindingBuilder builder;
|
protected BaseSAML2BindingBuilder builder;
|
||||||
|
|
||||||
|
@ -170,7 +170,9 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
||||||
String str = builder.buildHtmlPostResponse(document, actionUrl, true);
|
String str = builder.buildHtmlPostResponse(document, actionUrl, true);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
public String getRelayState() {
|
||||||
|
return relayState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,12 +301,13 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
||||||
parentNode.replaceChild(clonedAssertionElement, originalAssertionElement);
|
parentNode.replaceChild(clonedAssertionElement, originalAssertionElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String buildHtmlPostResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
public String buildHtmlPostResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
||||||
byte[] responseBytes = org.keycloak.saml.common.util.DocumentUtil.getDocumentAsString(responseDoc).getBytes(GeneralConstants.SAML_CHARSET);
|
return buildHtml(getSAMLResponse(responseDoc), actionUrl, asRequest);
|
||||||
String samlResponse = PostBindingUtil.base64Encode(new String(responseBytes, GeneralConstants.SAML_CHARSET));
|
}
|
||||||
|
|
||||||
return buildHtml(samlResponse, actionUrl, asRequest);
|
public static String getSAMLResponse(Document responseDoc) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
byte[] responseBytes = org.keycloak.saml.common.util.DocumentUtil.getDocumentAsString(responseDoc).getBytes(GeneralConstants.SAML_CHARSET);
|
||||||
|
return PostBindingUtil.base64Encode(new String(responseBytes, GeneralConstants.SAML_CHARSET));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String buildHtml(String samlResponse, String actionUrl, boolean asRequest) {
|
public String buildHtml(String samlResponse, String actionUrl, boolean asRequest) {
|
||||||
|
@ -319,13 +322,15 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
||||||
builder.append("<HTML>")
|
builder.append("<HTML>")
|
||||||
.append("<HEAD>")
|
.append("<HEAD>")
|
||||||
|
|
||||||
.append("<TITLE>SAML HTTP Post Binding</TITLE>")
|
.append("<TITLE>Authentication Redirect</TITLE>")
|
||||||
.append("</HEAD>")
|
.append("</HEAD>")
|
||||||
.append("<BODY Onload=\"document.forms[0].submit()\">")
|
.append("<BODY Onload=\"document.forms[0].submit()\">")
|
||||||
|
|
||||||
.append("<FORM METHOD=\"POST\" ACTION=\"").append(actionUrl).append("\">")
|
.append("<FORM METHOD=\"POST\" ACTION=\"").append(actionUrl).append("\">")
|
||||||
.append("<INPUT TYPE=\"HIDDEN\" NAME=\"").append(key).append("\"").append(" VALUE=\"").append(samlResponse).append("\"/>");
|
.append("<INPUT TYPE=\"HIDDEN\" NAME=\"").append(key).append("\"").append(" VALUE=\"").append(samlResponse).append("\"/>");
|
||||||
|
|
||||||
|
builder.append("<p>Redirecting, please wait.</p>");
|
||||||
|
|
||||||
if (isNotNull(relayState)) {
|
if (isNotNull(relayState)) {
|
||||||
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"").append(escapeAttribute(relayState)).append("\"/>");
|
builder.append("<INPUT TYPE=\"HIDDEN\" NAME=\"RelayState\" " + "VALUE=\"").append(escapeAttribute(relayState)).append("\"/>");
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,6 @@ public enum LoginFormsPages {
|
||||||
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
|
LOGIN, LOGIN_TOTP, LOGIN_CONFIG_TOTP, LOGIN_VERIFY_EMAIL,
|
||||||
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
|
LOGIN_IDP_LINK_CONFIRM, LOGIN_IDP_LINK_EMAIL,
|
||||||
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE,
|
OAUTH_GRANT, LOGIN_RESET_PASSWORD, LOGIN_UPDATE_PASSWORD, REGISTER, INFO, ERROR, LOGIN_UPDATE_PROFILE,
|
||||||
LOGIN_PAGE_EXPIRED, CODE, X509_CONFIRM;
|
LOGIN_PAGE_EXPIRED, CODE, X509_CONFIRM, SAML_POST_FORM;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@
|
||||||
package org.keycloak.forms.login;
|
package org.keycloak.forms.login;
|
||||||
|
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
@ -83,6 +81,8 @@ public interface LoginFormsProvider extends Provider {
|
||||||
|
|
||||||
Response createX509ConfirmPage();
|
Response createX509ConfirmPage();
|
||||||
|
|
||||||
|
Response createSamlPostForm();
|
||||||
|
|
||||||
LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
|
LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
|
||||||
|
|
||||||
LoginFormsProvider setClientSessionCode(String accessCode);
|
LoginFormsProvider setClientSessionCode(String accessCode);
|
||||||
|
|
|
@ -315,7 +315,7 @@ public class SAMLEndpoint {
|
||||||
builder.logoutRequestID(request.getID());
|
builder.logoutRequestID(request.getID());
|
||||||
builder.destination(config.getSingleLogoutServiceUrl());
|
builder.destination(config.getSingleLogoutServiceUrl());
|
||||||
builder.issuer(issuerURL);
|
builder.issuer(issuerURL);
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session)
|
||||||
.relayState(relayState);
|
.relayState(relayState);
|
||||||
boolean postBinding = config.isPostBindingLogout();
|
boolean postBinding = config.isPostBindingLogout();
|
||||||
if (config.isWantAuthnRequestsSigned()) {
|
if (config.isWantAuthnRequestsSigned()) {
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
.forceAuthn(getConfig().isForceAuthn())
|
.forceAuthn(getConfig().isForceAuthn())
|
||||||
.protocolBinding(protocolBinding)
|
.protocolBinding(protocolBinding)
|
||||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session)
|
||||||
.relayState(request.getState().getEncoded());
|
.relayState(request.getState().getEncoded());
|
||||||
boolean postBinding = getConfig().isPostBindingAuthnRequest();
|
boolean postBinding = getConfig().isPostBindingAuthnRequest();
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
|
private JaxrsSAML2BindingBuilder buildLogoutBinding(KeycloakSession session, UserSessionModel userSession, RealmModel realm) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session)
|
||||||
.relayState(userSession.getId());
|
.relayState(userSession.getId());
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.keycloak.forms.login.freemarker.model.ProfileBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
import org.keycloak.forms.login.freemarker.model.RealmBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.RegisterBean;
|
import org.keycloak.forms.login.freemarker.model.RegisterBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.RequiredActionUrlFormatterMethod;
|
import org.keycloak.forms.login.freemarker.model.RequiredActionUrlFormatterMethod;
|
||||||
|
import org.keycloak.forms.login.freemarker.model.SAMLPostFormBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.TotpBean;
|
import org.keycloak.forms.login.freemarker.model.TotpBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.UrlBean;
|
import org.keycloak.forms.login.freemarker.model.UrlBean;
|
||||||
import org.keycloak.forms.login.freemarker.model.X509ConfirmBean;
|
import org.keycloak.forms.login.freemarker.model.X509ConfirmBean;
|
||||||
|
@ -209,6 +210,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
case X509_CONFIRM:
|
case X509_CONFIRM:
|
||||||
attributes.put("x509", new X509ConfirmBean(formData));
|
attributes.put("x509", new X509ConfirmBean(formData));
|
||||||
break;
|
break;
|
||||||
|
case SAML_POST_FORM:
|
||||||
|
attributes.put("samlPost", new SAMLPostFormBean(formData));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return processTemplate(theme, Templates.getTemplate(page), locale);
|
return processTemplate(theme, Templates.getTemplate(page), locale);
|
||||||
|
@ -521,6 +525,11 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
return createResponse(LoginFormsPages.X509_CONFIRM);
|
return createResponse(LoginFormsPages.X509_CONFIRM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response createSamlPostForm() {
|
||||||
|
return createResponse(LoginFormsPages.SAML_POST_FORM);
|
||||||
|
}
|
||||||
|
|
||||||
protected void setMessage(MessageType type, String message, Object... parameters) {
|
protected void setMessage(MessageType type, String message, Object... parameters) {
|
||||||
messageType = type;
|
messageType = type;
|
||||||
messages = new ArrayList<>();
|
messages = new ArrayList<>();
|
||||||
|
|
|
@ -58,6 +58,8 @@ public class Templates {
|
||||||
return "login-page-expired.ftl";
|
return "login-page-expired.ftl";
|
||||||
case X509_CONFIRM:
|
case X509_CONFIRM:
|
||||||
return "login-x509-info.ftl";
|
return "login-x509-info.ftl";
|
||||||
|
case SAML_POST_FORM:
|
||||||
|
return "saml-post-form.ftl";
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.keycloak.forms.login.freemarker.model;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
|
||||||
|
public class SAMLPostFormBean {
|
||||||
|
|
||||||
|
private final String samlRequest;
|
||||||
|
private final String samlResponse;
|
||||||
|
private final String relayState;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
public SAMLPostFormBean(MultivaluedMap<String, String> formData) {
|
||||||
|
samlRequest = formData.getFirst(GeneralConstants.SAML_REQUEST_KEY);
|
||||||
|
samlResponse = formData.getFirst(GeneralConstants.SAML_RESPONSE_KEY);
|
||||||
|
relayState = formData.getFirst(GeneralConstants.RELAY_STATE);
|
||||||
|
url = formData.getFirst(GeneralConstants.URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSAMLRequest() {
|
||||||
|
return samlRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSAMLResponse() {
|
||||||
|
return samlResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRelayState() {
|
||||||
|
return relayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,13 +17,17 @@
|
||||||
|
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import javax.ws.rs.core.CacheControl;
|
import javax.ws.rs.core.CacheControl;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -33,25 +37,37 @@ import java.net.URI;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2BindingBuilder> {
|
public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2BindingBuilder> {
|
||||||
public static class PostBindingBuilder extends BasePostBindingBuilder {
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
|
public JaxrsSAML2BindingBuilder(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PostBindingBuilder extends BasePostBindingBuilder {
|
||||||
public PostBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
|
public PostBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
|
||||||
super(builder, document);
|
super(builder, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response request(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
public Response request(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
||||||
return buildResponse(document, actionUrl, true);
|
return createResponse(actionUrl, GeneralConstants.SAML_REQUEST_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
|
||||||
return buildResponse(document, actionUrl, false);
|
return createResponse(actionUrl, GeneralConstants.SAML_RESPONSE_KEY);
|
||||||
}
|
|
||||||
protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
|
|
||||||
String str = builder.buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
|
|
||||||
|
|
||||||
return Response.ok(str, MediaType.TEXT_HTML_TYPE)
|
|
||||||
.header("Pragma", "no-cache")
|
|
||||||
.header("Cache-Control", "no-cache, no-store").build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response createResponse(String actionUrl, String key) throws ProcessingException, ConfigurationException, IOException {
|
||||||
|
MultivaluedMap<String,String> formData = new MultivaluedHashMap<>();
|
||||||
|
formData.add(GeneralConstants.URL, actionUrl);
|
||||||
|
formData.add(key, BaseSAML2BindingBuilder.getSAMLResponse(document));
|
||||||
|
|
||||||
|
if (this.getRelayState() != null) {
|
||||||
|
formData.add(GeneralConstants.RELAY_STATE, this.getRelayState());
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.getProvider(LoginFormsProvider.class).setFormData(formData).createSamlPostForm();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RedirectBindingBuilder extends BaseRedirectBindingBuilder {
|
public static class RedirectBindingBuilder extends BaseRedirectBindingBuilder {
|
||||||
|
@ -79,10 +95,12 @@ public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public RedirectBindingBuilder redirectBinding(Document document) throws ProcessingException {
|
public RedirectBindingBuilder redirectBinding(Document document) throws ProcessingException {
|
||||||
return new RedirectBindingBuilder(this, document);
|
return new RedirectBindingBuilder(this, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public PostBindingBuilder postBinding(Document document) throws ProcessingException {
|
public PostBindingBuilder postBinding(Document document) throws ProcessingException {
|
||||||
return new PostBindingBuilder(this, document);
|
return new PostBindingBuilder(this, document);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer;
|
||||||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
|
||||||
import org.keycloak.services.managers.ResourceAdminManager;
|
import org.keycloak.services.managers.ResourceAdminManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
@ -83,7 +82,6 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,7 +190,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
private Response samlErrorMessage(
|
private Response samlErrorMessage(
|
||||||
AuthenticationSessionModel authSession, SamlClient samlClient, boolean isPostBinding,
|
AuthenticationSessionModel authSession, SamlClient samlClient, boolean isPostBinding,
|
||||||
String destination, JBossSAMLURIConstants statusDetail, String relayState) {
|
String destination, JBossSAMLURIConstants statusDetail, String relayState) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(relayState);
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session).relayState(relayState);
|
||||||
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(destination).issuer(getResponseIssuer(realm)).status(statusDetail.get());
|
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(destination).issuer(getResponseIssuer(realm)).status(statusDetail.get());
|
||||||
KeyManager keyManager = session.keys();
|
KeyManager keyManager = session.keys();
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
|
@ -451,7 +449,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
|
return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.FAILED_TO_PROCESS_RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(session);
|
||||||
bindingBuilder.relayState(relayState);
|
bindingBuilder.relayState(relayState);
|
||||||
|
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
|
@ -597,7 +595,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
|
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
|
||||||
builder.destination(logoutBindingUri);
|
builder.destination(logoutBindingUri);
|
||||||
builder.issuer(getResponseIssuer(realm));
|
builder.issuer(getResponseIssuer(realm));
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session);
|
||||||
binding.relayState(logoutRelayState);
|
binding.relayState(logoutRelayState);
|
||||||
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
|
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
|
||||||
boolean postBinding = isLogoutPostBindingForInitiator(userSession);
|
boolean postBinding = isLogoutPostBindingForInitiator(userSession);
|
||||||
|
@ -725,7 +723,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
private JaxrsSAML2BindingBuilder createBindingBuilder(SamlClient samlClient) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session);
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
|
||||||
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
|
||||||
|
|
|
@ -458,7 +458,7 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
builder.logoutRequestID(logoutRequest.getID());
|
builder.logoutRequestID(logoutRequest.getID());
|
||||||
builder.destination(logoutBindingUri);
|
builder.destination(logoutBindingUri);
|
||||||
builder.issuer(RealmsResource.realmBaseUrl(session.getContext().getUri()).build(realm.getName()).toString());
|
builder.issuer(RealmsResource.realmBaseUrl(session.getContext().getUri()).build(realm.getName()).toString());
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder(session).relayState(logoutRelayState);
|
||||||
boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
|
boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
|
||||||
if (samlClient.requiresRealmSignature()) {
|
if (samlClient.requiresRealmSignature()) {
|
||||||
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
|
SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
|
||||||
|
|
|
@ -39,13 +39,17 @@ import java.util.stream.Collectors;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
|
||||||
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
|
||||||
|
import org.keycloak.testsuite.updaters.IdentityProviderCreator;
|
||||||
|
import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
||||||
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
import static org.keycloak.testsuite.util.Matchers.bodyHC;
|
||||||
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
|
||||||
|
|
||||||
|
@ -184,4 +188,42 @@ public class IdpInitiatedLoginTest extends AbstractSamlTest {
|
||||||
adminClient.realm(REALM_NAME).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep)
|
adminClient.realm(REALM_NAME).clients().get(clientRep.getId()).update(ClientBuilder.edit(clientRep)
|
||||||
.protocol(SamlProtocol.LOGIN_PROTOCOL).build());
|
.protocol(SamlProtocol.LOGIN_PROTOCOL).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSamlPostBindingPageLogin() {
|
||||||
|
new SamlClientBuilder()
|
||||||
|
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||||
|
.login().user(bburkeUser).build()
|
||||||
|
.execute(r -> {
|
||||||
|
Assert.assertThat(r, statusCodeIsHC(Response.Status.OK));
|
||||||
|
Assert.assertThat(r, bodyHC(allOf(
|
||||||
|
containsString("Redirecting, please wait."),
|
||||||
|
containsString("<input type=\"hidden\" name=\"SAMLResponse\""),
|
||||||
|
containsString("<h1 id=\"kc-page-title\">")
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSamlPostBindingPageIdP() throws Exception {
|
||||||
|
try (IdentityProviderCreator idp = new IdentityProviderCreator(adminClient.realm(REALM_NAME),
|
||||||
|
IdentityProviderBuilder.create()
|
||||||
|
.alias("saml-idp")
|
||||||
|
.providerId("saml")
|
||||||
|
.setAttribute(SAMLIdentityProviderConfig.SINGLE_SIGN_ON_SERVICE_URL, "http://saml-idp-sso-service/")
|
||||||
|
.setAttribute(SAMLIdentityProviderConfig.POST_BINDING_AUTHN_REQUEST, "true")
|
||||||
|
.build())) {
|
||||||
|
new SamlClientBuilder()
|
||||||
|
.idpInitiatedLogin(getAuthServerSamlEndpoint(REALM_NAME), "sales-post").build()
|
||||||
|
.login().idp("saml-idp").build()
|
||||||
|
.execute(r -> {
|
||||||
|
Assert.assertThat(r, statusCodeIsHC(Response.Status.OK));
|
||||||
|
Assert.assertThat(r, bodyHC(allOf(
|
||||||
|
containsString("Redirecting, please wait."),
|
||||||
|
containsString("<input type=\"hidden\" name=\"SAMLRequest\""),
|
||||||
|
containsString("<h1 id=\"kc-page-title\">")
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -316,3 +316,8 @@ openshift.scope.user_info=User information
|
||||||
openshift.scope.user_check-access=User access information
|
openshift.scope.user_check-access=User access information
|
||||||
openshift.scope.user_full=Full Access
|
openshift.scope.user_full=Full Access
|
||||||
openshift.scope.list-projects=List projects
|
openshift.scope.list-projects=List projects
|
||||||
|
|
||||||
|
# SAML authentication
|
||||||
|
saml.post-form.title=Authentication Redirect
|
||||||
|
saml.post-form.message=Redirecting, please wait.
|
||||||
|
saml.post-form.js-disabled=JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue.
|
|
@ -0,0 +1,25 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.registrationLayout; section>
|
||||||
|
<#if section = "header">
|
||||||
|
${kcSanitize(msg("saml.post-form.title"))}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<script>window.onload = function() {document.forms[0].submit()};</script>
|
||||||
|
<p>${kcSanitize(msg("saml.post-form.message"))}</p>
|
||||||
|
<form name="saml-post-binding" method="post" action="${samlPost.url}">
|
||||||
|
<#if samlPost.SAMLRequest??>
|
||||||
|
<input type="hidden" name="SAMLRequest" value="${samlPost.SAMLRequest}"/>
|
||||||
|
</#if>
|
||||||
|
<#if samlPost.SAMLResponse??>
|
||||||
|
<input type="hidden" name="SAMLResponse" value="${samlPost.SAMLResponse}"/>
|
||||||
|
</#if>
|
||||||
|
<#if samlPost.relayState??>
|
||||||
|
<input type="hidden" name="RelayState" value="${samlPost.relayState}"/>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p>${kcSanitize(msg("saml.post-form.js-disabled"))}</p>
|
||||||
|
<input type="submit" value="${kcSanitize(msg("doContinue"))}"/>
|
||||||
|
</noscript>
|
||||||
|
</form>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
Loading…
Reference in a new issue