KEYCLOAK-2075 - support for IsPassive mode in SAML IdP endpoint
This commit is contained in:
parent
274838aa64
commit
0bdb05e152
9 changed files with 383 additions and 425 deletions
|
@ -1,5 +1,22 @@
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
|
@ -36,30 +53,14 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
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;
|
||||||
import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
|
import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
|
||||||
import org.keycloak.services.ErrorPage;
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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 $
|
||||||
|
@ -67,7 +68,6 @@ import java.util.UUID;
|
||||||
public class SamlProtocol implements LoginProtocol {
|
public class SamlProtocol implements LoginProtocol {
|
||||||
protected static final Logger logger = Logger.getLogger(SamlProtocol.class);
|
protected static final Logger logger = Logger.getLogger(SamlProtocol.class);
|
||||||
|
|
||||||
|
|
||||||
public static final String ATTRIBUTE_TRUE_VALUE = "true";
|
public static final String ATTRIBUTE_TRUE_VALUE = "true";
|
||||||
public static final String ATTRIBUTE_FALSE_VALUE = "false";
|
public static final String ATTRIBUTE_FALSE_VALUE = "false";
|
||||||
public static final String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
public static final String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
|
||||||
|
@ -115,7 +115,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
protected EventBuilder event;
|
protected EventBuilder event;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlProtocol setSession(KeycloakSession session) {
|
public SamlProtocol setSession(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
@ -135,7 +134,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlProtocol setHttpHeaders(HttpHeaders headers){
|
public SamlProtocol setHttpHeaders(HttpHeaders headers) {
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -146,22 +145,62 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
public Response sendError(ClientSessionModel clientSession, Error error) {
|
||||||
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
|
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
|
||||||
|
session.sessions().removeClientSession(realm, clientSession);
|
||||||
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
|
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
|
||||||
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
|
if (error == Error.CANCELLED_BY_USER) {
|
||||||
Map<String, String> params = new HashMap<>();
|
UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
|
||||||
params.put("realm", realm.getName());
|
Map<String, String> params = new HashMap<>();
|
||||||
params.put("protocol", LOGIN_PROTOCOL);
|
params.put("realm", realm.getName());
|
||||||
params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
|
params.put("protocol", LOGIN_PROTOCOL);
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
|
||||||
URI redirect = builder.buildFromMap(params);
|
URI redirect = builder.buildFromMap(params);
|
||||||
return Response.status(302).location(redirect).build();
|
return Response.status(302).location(redirect).build();
|
||||||
|
} else {
|
||||||
|
return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(clientSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).get());
|
||||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
try {
|
||||||
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
|
||||||
|
Document document = builder.buildDocument();
|
||||||
|
if (isPostBinding(clientSession)) {
|
||||||
|
return binding.postBinding(document).response(clientSession.getRedirectUri());
|
||||||
|
} else {
|
||||||
|
return binding.redirectBinding(document).response(clientSession.getRedirectUri());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JBossSAMLURIConstants translateErrorToSAMLStatus(Error error) {
|
||||||
|
switch (error) {
|
||||||
|
case CANCELLED_BY_USER:
|
||||||
|
case CONSENT_DENIED:
|
||||||
|
return JBossSAMLURIConstants.STATUS_REQUEST_DENIED;
|
||||||
|
case PASSIVE_INTERACTION_REQUIRED:
|
||||||
|
case PASSIVE_LOGIN_REQUIRED:
|
||||||
|
return JBossSAMLURIConstants.STATUS_NO_PASSIVE;
|
||||||
|
default:
|
||||||
|
logger.warn("Untranslated protocol Error: " + error.name() + " so we return default SAML error");
|
||||||
|
return JBossSAMLURIConstants.STATUS_REQUEST_DENIED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String translateErrorToIdpInitiatedErrorMessage(Error error) {
|
||||||
|
switch (error) {
|
||||||
|
case CONSENT_DENIED:
|
||||||
|
return Messages.CONSENT_DENIED;
|
||||||
|
case PASSIVE_INTERACTION_REQUIRED:
|
||||||
|
case PASSIVE_LOGIN_REQUIRED:
|
||||||
|
return Messages.UNEXPECTED_ERROR_HANDLING_REQUEST;
|
||||||
|
default:
|
||||||
|
logger.warn("Untranslated protocol Error: " + error.name() + " so we return default error message");
|
||||||
|
return Messages.UNEXPECTED_ERROR_HANDLING_REQUEST;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,25 +208,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
|
return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response getErrorResponse(ClientSessionModel clientSession, String status) {
|
|
||||||
SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder()
|
|
||||||
.destination(clientSession.getRedirectUri())
|
|
||||||
.issuer(getResponseIssuer(realm))
|
|
||||||
.status(status);
|
|
||||||
try {
|
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
|
||||||
.relayState(clientSession.getNote(GeneralConstants.RELAY_STATE));
|
|
||||||
Document document = builder.buildDocument();
|
|
||||||
if (isPostBinding(clientSession)) {
|
|
||||||
return binding.postBinding(document).response(clientSession.getRedirectUri());
|
|
||||||
} else {
|
|
||||||
return binding.redirectBinding(document).response(clientSession.getRedirectUri());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isPostBinding(ClientSessionModel clientSession) {
|
protected boolean isPostBinding(ClientSessionModel clientSession) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || forcePostBinding(client);
|
return SamlProtocol.SAML_POST_BINDING.equals(clientSession.getNote(SamlProtocol.SAML_BINDING)) || forcePostBinding(client);
|
||||||
|
@ -198,8 +218,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return SamlProtocol.SAML_POST_BINDING.equals(note);
|
return SamlProtocol.SAML_POST_BINDING.equals(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected boolean isLogoutPostBindingForClient(ClientSessionModel clientSession) {
|
protected boolean isLogoutPostBindingForClient(ClientSessionModel clientSession) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
String logoutPostUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
|
String logoutPostUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
|
||||||
|
@ -207,7 +225,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
if (logoutPostUrl == null) {
|
if (logoutPostUrl == null) {
|
||||||
// if we don't have a redirect uri either, return true and default to the admin url + POST binding
|
// if we don't have a redirect uri either, return true and default to the admin url + POST binding
|
||||||
if (logoutRedirectUrl == null) return true;
|
if (logoutRedirectUrl == null)
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,11 +237,13 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
String bindingType = clientSession.getNote(SAML_BINDING);
|
String bindingType = clientSession.getNote(SAML_BINDING);
|
||||||
|
|
||||||
// if the login binding was POST, return true
|
// if the login binding was POST, return true
|
||||||
if (SAML_POST_BINDING.equals(bindingType)) return true;
|
if (SAML_POST_BINDING.equals(bindingType))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (logoutRedirectUrl == null) return true; // we don't have a redirect binding url, so use post binding
|
if (logoutRedirectUrl == null)
|
||||||
|
return true; // we don't have a redirect binding url, so use post binding
|
||||||
|
|
||||||
return false; // redirect binding
|
return false; // redirect binding
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +269,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
|
nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(nameIdFormat == null) return SAML_DEFAULT_NAMEID_FORMAT;
|
if (nameIdFormat == null)
|
||||||
|
return SAML_DEFAULT_NAMEID_FORMAT;
|
||||||
return nameIdFormat;
|
return nameIdFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,20 +281,21 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
protected String getNameId(String nameIdFormat, ClientSessionModel clientSession, UserSessionModel userSession) {
|
protected String getNameId(String nameIdFormat, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||||
return userSession.getUser().getEmail();
|
return userSession.getUser().getEmail();
|
||||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())) {
|
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get())) {
|
||||||
// "G-" stands for "generated" Add this for the slight possibility of collisions.
|
// "G-" stands for "generated" Add this for the slight possibility of collisions.
|
||||||
return "G-" + UUID.randomUUID().toString();
|
return "G-" + UUID.randomUUID().toString();
|
||||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())) {
|
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())) {
|
||||||
// generate a persistent user id specifically for each client.
|
// generate a persistent user id specifically for each client.
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
|
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
|
||||||
String samlPersistentId = user.getFirstAttribute(name);
|
String samlPersistentId = user.getFirstAttribute(name);
|
||||||
if (samlPersistentId != null) return samlPersistentId;
|
if (samlPersistentId != null)
|
||||||
|
return samlPersistentId;
|
||||||
// "G-" stands for "generated"
|
// "G-" stands for "generated"
|
||||||
samlPersistentId = "G-" + UUID.randomUUID().toString();
|
samlPersistentId = "G-" + UUID.randomUUID().toString();
|
||||||
user.setSingleAttribute(name, samlPersistentId);
|
user.setSingleAttribute(name, samlPersistentId);
|
||||||
return samlPersistentId;
|
return samlPersistentId;
|
||||||
} else if(nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())){
|
} else if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
|
||||||
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
// TODO: Support for persistent NameID (pseudo-random identifier persisted in user object)
|
||||||
return userSession.getUser().getUsername();
|
return userSession.getUser().getUsername();
|
||||||
} else {
|
} else {
|
||||||
|
@ -297,15 +320,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
|
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
|
||||||
|
|
||||||
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
|
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
|
||||||
builder.requestID(requestID)
|
builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan()).sessionIndex(clientSession.getId())
|
||||||
.destination(redirectUri)
|
.requestIssuer(clientSession.getClient().getClientId()).nameIdentifier(nameIdFormat, nameId).authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
||||||
.issuer(responseIssuer)
|
|
||||||
.assertionExpiration(realm.getAccessCodeLifespan())
|
|
||||||
.subjectExpiration(realm.getAccessTokenLifespan())
|
|
||||||
.sessionIndex(clientSession.getId())
|
|
||||||
.requestIssuer(clientSession.getClient().getClientId())
|
|
||||||
.nameIdentifier(nameIdFormat, nameId)
|
|
||||||
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
|
||||||
if (!includeAuthnStatement(client)) {
|
if (!includeAuthnStatement(client)) {
|
||||||
builder.disableAuthnStatement(true);
|
builder.disableAuthnStatement(true);
|
||||||
}
|
}
|
||||||
|
@ -317,20 +333,20 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
Set<ProtocolMapperModel> mappings = accessCode.getRequestedProtocolMappers();
|
Set<ProtocolMapperModel> mappings = accessCode.getRequestedProtocolMappers();
|
||||||
for (ProtocolMapperModel mapping : mappings) {
|
for (ProtocolMapperModel mapping : mappings) {
|
||||||
|
|
||||||
ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
ProtocolMapper mapper = (ProtocolMapper) session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
|
||||||
if (mapper == null) continue;
|
if (mapper == null)
|
||||||
|
continue;
|
||||||
if (mapper instanceof SAMLAttributeStatementMapper) {
|
if (mapper instanceof SAMLAttributeStatementMapper) {
|
||||||
attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper)mapper, mapping));
|
attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper) mapper, mapping));
|
||||||
}
|
}
|
||||||
if (mapper instanceof SAMLLoginResponseMapper) {
|
if (mapper instanceof SAMLLoginResponseMapper) {
|
||||||
loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper)mapper, mapping));
|
loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper) mapper, mapping));
|
||||||
}
|
}
|
||||||
if (mapper instanceof SAMLRoleListMapper) {
|
if (mapper instanceof SAMLRoleListMapper) {
|
||||||
roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper)mapper, mapping);
|
roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper) mapper, mapping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Document samlDocument = null;
|
Document samlDocument = null;
|
||||||
try {
|
try {
|
||||||
ResponseType samlModel = builder.buildModel();
|
ResponseType samlModel = builder.buildModel();
|
||||||
|
@ -351,18 +367,14 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
bindingBuilder.canonicalizationMethod(canonicalization);
|
bindingBuilder.canonicalizationMethod(canonicalization);
|
||||||
}
|
}
|
||||||
bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
|
bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
|
||||||
.signDocument();
|
|
||||||
}
|
}
|
||||||
if (requiresAssertionSignature(client)) {
|
if (requiresAssertionSignature(client)) {
|
||||||
String canonicalization = client.getAttribute(SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
|
String canonicalization = client.getAttribute(SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
bindingBuilder.canonicalizationMethod(canonicalization);
|
bindingBuilder.canonicalizationMethod(canonicalization);
|
||||||
}
|
}
|
||||||
bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
|
bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signAssertions();
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
|
||||||
.signAssertions();
|
|
||||||
}
|
}
|
||||||
if (requiresEncryption(client)) {
|
if (requiresEncryption(client)) {
|
||||||
PublicKey publicKey = null;
|
PublicKey publicKey = null;
|
||||||
|
@ -402,7 +414,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
|
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
|
||||||
if (alg != null) {
|
if (alg != null) {
|
||||||
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
|
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
|
||||||
if (algorithm != null) return algorithm;
|
if (algorithm != null)
|
||||||
|
return algorithm;
|
||||||
}
|
}
|
||||||
return SignatureAlgorithm.RSA_SHA256;
|
return SignatureAlgorithm.RSA_SHA256;
|
||||||
}
|
}
|
||||||
|
@ -421,10 +434,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void transformAttributeStatement(List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers,
|
public void transformAttributeStatement(List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers, ResponseType response, KeycloakSession session, UserSessionModel userSession,
|
||||||
ResponseType response,
|
ClientSessionModel clientSession) {
|
||||||
KeycloakSession session,
|
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
|
||||||
AssertionType assertion = response.getAssertions().get(0).getAssertion();
|
AssertionType assertion = response.getAssertions().get(0).getAssertion();
|
||||||
AttributeStatementType attributeStatement = new AttributeStatementType();
|
AttributeStatementType attributeStatement = new AttributeStatementType();
|
||||||
|
|
||||||
|
@ -432,50 +443,32 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
processor.mapper.transformAttributeStatement(attributeStatement, processor.model, session, userSession, clientSession);
|
processor.mapper.transformAttributeStatement(attributeStatement, processor.model, session, userSession, clientSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
//SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
|
// SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
|
||||||
if(attributeStatement.getAttributes().size() > 0) {
|
if (attributeStatement.getAttributes().size() > 0) {
|
||||||
assertion.addStatement(attributeStatement);
|
assertion.addStatement(attributeStatement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers,
|
public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
ResponseType response,
|
|
||||||
KeycloakSession session,
|
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
|
||||||
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
|
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
|
||||||
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
|
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void populateRoles(ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper,
|
public void populateRoles(ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
ResponseType response,
|
if (roleListMapper == null)
|
||||||
KeycloakSession session,
|
return;
|
||||||
UserSessionModel userSession, ClientSessionModel clientSession) {
|
|
||||||
if (roleListMapper == null) return;
|
|
||||||
AssertionType assertion = response.getAssertions().get(0).getAssertion();
|
AssertionType assertion = response.getAssertions().get(0).getAssertion();
|
||||||
AttributeStatementType attributeStatement = new AttributeStatementType();
|
AttributeStatementType attributeStatement = new AttributeStatementType();
|
||||||
roleListMapper.mapper.mapRoles(attributeStatement, roleListMapper.model, session, userSession, clientSession);
|
roleListMapper.mapper.mapRoles(attributeStatement, roleListMapper.model, session, userSession, clientSession);
|
||||||
|
|
||||||
//SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
|
// SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
|
||||||
if(attributeStatement.getAttributes().size() > 0) {
|
if (attributeStatement.getAttributes().size() > 0) {
|
||||||
assertion.addStatement(attributeStatement);
|
assertion.addStatement(attributeStatement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response consentDenied(ClientSessionModel clientSession) {
|
|
||||||
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
|
|
||||||
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
|
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
|
||||||
return ErrorPage.error(session, Messages.CONSENT_DENIED);
|
|
||||||
} else {
|
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
|
||||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getLogoutServiceUrl(UriInfo uriInfo, ClientModel client, String bindingType) {
|
public static String getLogoutServiceUrl(UriInfo uriInfo, ClientModel client, String bindingType) {
|
||||||
String logoutServiceUrl = null;
|
String logoutServiceUrl = null;
|
||||||
if (SAML_POST_BINDING.equals(bindingType)) {
|
if (SAML_POST_BINDING.equals(bindingType)) {
|
||||||
|
@ -483,8 +476,10 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
} else {
|
} else {
|
||||||
logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
|
logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
|
||||||
}
|
}
|
||||||
if (logoutServiceUrl == null && client instanceof ClientModel) logoutServiceUrl = ((ClientModel)client).getManagementUrl();
|
if (logoutServiceUrl == null && client instanceof ClientModel)
|
||||||
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) return null;
|
logoutServiceUrl = ((ClientModel) client).getManagementUrl();
|
||||||
|
if (logoutServiceUrl == null || logoutServiceUrl.trim().equals(""))
|
||||||
|
return null;
|
||||||
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
|
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -492,7 +487,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
@Override
|
@Override
|
||||||
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
if (!(client instanceof ClientModel)) return null;
|
if (!(client instanceof ClientModel))
|
||||||
|
return null;
|
||||||
try {
|
try {
|
||||||
if (isLogoutPostBindingForClient(clientSession)) {
|
if (isLogoutPostBindingForClient(clientSession)) {
|
||||||
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
|
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
|
||||||
|
@ -541,9 +537,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
if (canonicalization != null) {
|
if (canonicalization != null) {
|
||||||
binding.canonicalizationMethod(canonicalization);
|
binding.canonicalizationMethod(canonicalization);
|
||||||
}
|
}
|
||||||
binding.signatureAlgorithm(algorithm)
|
binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
|
||||||
.signDocument();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -561,8 +555,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
|
@ -573,7 +565,6 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
|
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
|
||||||
|
|
||||||
|
|
||||||
String logoutRequestString = null;
|
String logoutRequestString = null;
|
||||||
try {
|
try {
|
||||||
JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
|
JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
|
||||||
|
@ -583,20 +574,21 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
|
HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
|
||||||
for (int i = 0; i < 2; i++) { // follow redirects once
|
for (int i = 0; i < 2; i++) { // follow redirects once
|
||||||
try {
|
try {
|
||||||
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||||
formparams.add(new BasicNameValuePair(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString));
|
formparams.add(new BasicNameValuePair(GeneralConstants.SAML_REQUEST_KEY, logoutRequestString));
|
||||||
formparams.add(new BasicNameValuePair("BACK_CHANNEL_LOGOUT", "BACK_CHANNEL_LOGOUT")); // for Picketlink todo remove this
|
formparams.add(new BasicNameValuePair("BACK_CHANNEL_LOGOUT", "BACK_CHANNEL_LOGOUT")); // for Picketlink
|
||||||
|
// todo remove
|
||||||
|
// this
|
||||||
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||||
HttpPost post = new HttpPost(logoutUrl);
|
HttpPost post = new HttpPost(logoutUrl);
|
||||||
post.setEntity(form);
|
post.setEntity(form);
|
||||||
HttpResponse response = httpClient.execute(post);
|
HttpResponse response = httpClient.execute(post);
|
||||||
try {
|
try {
|
||||||
int status = response.getStatusLine().getStatusCode();
|
int status = response.getStatusLine().getStatusCode();
|
||||||
if (status == 302 && !logoutUrl.endsWith("/")) {
|
if (status == 302 && !logoutUrl.endsWith("/")) {
|
||||||
String redirect = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
|
String redirect = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
|
||||||
String withSlash = logoutUrl + "/";
|
String withSlash = logoutUrl + "/";
|
||||||
if (withSlash.equals(redirect)) {
|
if (withSlash.equals(redirect)) {
|
||||||
|
@ -608,7 +600,8 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
InputStream is = entity.getContent();
|
InputStream is = entity.getContent();
|
||||||
if (is != null) is.close();
|
if (is != null)
|
||||||
|
is.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -622,21 +615,15 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
|
|
||||||
protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, ClientSessionModel clientSession, ClientModel client) {
|
protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, ClientSessionModel clientSession, ClientModel client) {
|
||||||
// build userPrincipal with subject used at login
|
// build userPrincipal with subject used at login
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)).sessionIndex(clientSession.getId())
|
||||||
.assertionExpiration(realm.getAccessCodeLifespan())
|
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl);
|
||||||
.issuer(getResponseIssuer(realm))
|
|
||||||
.sessionIndex(clientSession.getId())
|
|
||||||
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
|
|
||||||
.destination(logoutUrl);
|
|
||||||
return logoutBuilder;
|
return logoutBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder createBindingBuilder(ClientModel client) {
|
private JaxrsSAML2BindingBuilder createBindingBuilder(ClientModel client) {
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
|
||||||
if (requiresRealmSignature(client)) {
|
if (requiresRealmSignature(client)) {
|
||||||
binding.signatureAlgorithm(getSignatureAlgorithm(client))
|
binding.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
|
||||||
.signDocument();
|
|
||||||
}
|
}
|
||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
|
||||||
import org.jboss.resteasy.spi.HttpResponse;
|
|
||||||
import org.keycloak.common.ClientConnection;
|
|
||||||
import org.keycloak.common.VerificationException;
|
import org.keycloak.common.VerificationException;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.common.util.StreamUtil;
|
||||||
import org.keycloak.dom.saml.v2.SAML2Object;
|
import org.keycloak.dom.saml.v2.SAML2Object;
|
||||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||||
|
@ -16,15 +28,12 @@ import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.AuthenticationFlowModel;
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
||||||
import org.keycloak.saml.SAMLRequestParser;
|
import org.keycloak.saml.SAMLRequestParser;
|
||||||
|
@ -33,33 +42,10 @@ import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.Urls;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.common.util.StreamUtil;
|
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
|
||||||
import javax.ws.rs.FormParam;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.PathParam;
|
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.QueryParam;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.SecurityContext;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import javax.ws.rs.ext.Providers;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resource class for the oauth/openid connect token service
|
* Resource class for the oauth/openid connect token service
|
||||||
|
@ -67,40 +53,12 @@ import java.util.List;
|
||||||
* @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 $
|
||||||
*/
|
*/
|
||||||
public class SamlService {
|
public class SamlService extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(SamlService.class);
|
protected static final Logger logger = Logger.getLogger(SamlService.class);
|
||||||
|
|
||||||
protected RealmModel realm;
|
|
||||||
private EventBuilder event;
|
|
||||||
protected AuthenticationManager authManager;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
protected Providers providers;
|
|
||||||
@Context
|
|
||||||
protected SecurityContext securityContext;
|
|
||||||
@Context
|
|
||||||
protected UriInfo uriInfo;
|
|
||||||
@Context
|
|
||||||
protected HttpHeaders headers;
|
|
||||||
@Context
|
|
||||||
protected HttpRequest request;
|
|
||||||
@Context
|
|
||||||
protected HttpResponse response;
|
|
||||||
@Context
|
|
||||||
protected KeycloakSession session;
|
|
||||||
@Context
|
|
||||||
protected ClientConnection clientConnection;
|
|
||||||
|
|
||||||
/*
|
|
||||||
@Context
|
|
||||||
protected ResourceContext resourceContext;
|
|
||||||
*/
|
|
||||||
|
|
||||||
public SamlService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
public SamlService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||||
this.realm = realm;
|
super(realm, event, authManager);
|
||||||
this.event = event;
|
|
||||||
this.authManager = authManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class BindingProtocol {
|
public abstract class BindingProtocol {
|
||||||
|
@ -243,7 +201,7 @@ public class SamlService {
|
||||||
bindingType = SamlProtocol.SAML_POST_BINDING;
|
bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||||
String redirect = null;
|
String redirect = null;
|
||||||
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
|
||||||
if (redirectUri != null && !"null".equals(redirectUri)) { // "null" is for testing purposes
|
if (redirectUri != null && !"null".equals(redirectUri)) { // "null" is for testing purposes
|
||||||
redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
|
||||||
} else {
|
} else {
|
||||||
if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
|
if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
|
||||||
|
@ -262,7 +220,6 @@ public class SamlService {
|
||||||
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
|
||||||
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
|
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
|
||||||
clientSession.setRedirectUri(redirect);
|
clientSession.setRedirectUri(redirect);
|
||||||
|
@ -286,13 +243,9 @@ public class SamlService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newBrowserAuthentication(clientSession);
|
return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String getBindingType(AuthnRequestType requestAbstractType) {
|
private String getBindingType(AuthnRequestType requestAbstractType) {
|
||||||
URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
|
URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
|
||||||
|
|
||||||
|
@ -308,10 +261,8 @@ public class SamlService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSupportedNameIdFormat(String nameIdFormat) {
|
private boolean isSupportedNameIdFormat(String nameIdFormat) {
|
||||||
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get()) ||
|
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get()) || nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()) || nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())
|
||||||
nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()) ||
|
|| nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
|
||||||
nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get()) ||
|
|
||||||
nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -340,7 +291,8 @@ public class SamlService {
|
||||||
userSession.setNote(SamlProtocol.SAML_LOGOUT_SIGNATURE_ALGORITHM, SamlProtocol.getSignatureAlgorithm(client).toString());
|
userSession.setNote(SamlProtocol.SAML_LOGOUT_SIGNATURE_ALGORITHM, SamlProtocol.getSignatureAlgorithm(client).toString());
|
||||||
|
|
||||||
}
|
}
|
||||||
if (relayState != null) userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
|
if (relayState != null)
|
||||||
|
userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
|
||||||
userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
|
userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
|
||||||
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
|
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
|
||||||
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, client.getAttribute(SamlProtocol.SAML_CANONICALIZATION_METHOD_ATTRIBUTE));
|
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, client.getAttribute(SamlProtocol.SAML_CANONICALIZATION_METHOD_ATTRIBUTE));
|
||||||
|
@ -356,7 +308,8 @@ public class SamlService {
|
||||||
} else if (logoutRequest.getSessionIndex() != null) {
|
} else if (logoutRequest.getSessionIndex() != null) {
|
||||||
for (String sessionIndex : logoutRequest.getSessionIndex()) {
|
for (String sessionIndex : logoutRequest.getSessionIndex()) {
|
||||||
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
|
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
|
||||||
if (clientSession == null) continue;
|
if (clientSession == null)
|
||||||
|
continue;
|
||||||
UserSessionModel userSession = clientSession.getUserSession();
|
UserSessionModel userSession = clientSession.getUserSession();
|
||||||
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
|
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
|
||||||
// remove requesting client from logout
|
// remove requesting client from logout
|
||||||
|
@ -391,13 +344,10 @@ public class SamlService {
|
||||||
builder.logoutRequestID(logoutRequest.getID());
|
builder.logoutRequestID(logoutRequest.getID());
|
||||||
builder.destination(logoutBindingUri);
|
builder.destination(logoutBindingUri);
|
||||||
builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
|
builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
|
||||||
.relayState(logoutRelayState);
|
|
||||||
if (SamlProtocol.requiresRealmSignature(client)) {
|
if (SamlProtocol.requiresRealmSignature(client)) {
|
||||||
SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
|
SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
|
||||||
binding.signatureAlgorithm(algorithm)
|
binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
|
||||||
.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
|
||||||
.signDocument();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -420,7 +370,6 @@ public class SamlService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected class PostBindingProtocol extends BindingProtocol {
|
protected class PostBindingProtocol extends BindingProtocol {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -443,12 +392,14 @@ public class SamlService {
|
||||||
return SamlProtocol.SAML_POST_BINDING;
|
return SamlProtocol.SAML_POST_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||||
Response response = basicChecks(samlRequest, samlResponse);
|
Response response = basicChecks(samlRequest, samlResponse);
|
||||||
if (response != null) return response;
|
if (response != null)
|
||||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
return response;
|
||||||
else return handleSamlResponse(samlResponse, relayState);
|
if (samlRequest != null)
|
||||||
|
return handleSamlRequest(samlRequest, relayState);
|
||||||
|
else
|
||||||
|
return handleSamlResponse(samlResponse, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -464,7 +415,6 @@ public class SamlService {
|
||||||
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
|
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||||
|
@ -480,74 +430,35 @@ public class SamlService {
|
||||||
return SamlProtocol.SAML_REDIRECT_BINDING;
|
return SamlProtocol.SAML_REDIRECT_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||||
Response response = basicChecks(samlRequest, samlResponse);
|
Response response = basicChecks(samlRequest, samlResponse);
|
||||||
if (response != null) return response;
|
if (response != null)
|
||||||
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
|
return response;
|
||||||
else return handleSamlResponse(samlResponse, relayState);
|
if (samlRequest != null)
|
||||||
|
return handleSamlRequest(samlRequest, relayState);
|
||||||
|
else
|
||||||
|
return handleSamlResponse(samlResponse, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) {
|
||||||
private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
return handleBrowserAuthenticationRequest(clientSession, new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo), isPassive);
|
||||||
logger.debug("Automatically redirect to identity provider: " + providerId);
|
|
||||||
return Response.temporaryRedirect(
|
|
||||||
Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode))
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Response newBrowserAuthentication(ClientSessionModel clientSession) {
|
|
||||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
|
||||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
|
||||||
if (identityProvider.isAuthenticateByDefault()) {
|
|
||||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AuthenticationFlowModel flow = realm.getBrowserFlow();
|
|
||||||
String flowId = flow.getId();
|
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
|
||||||
processor.setClientSession(clientSession)
|
|
||||||
.setFlowPath(LoginActionsService.AUTHENTICATE_PATH)
|
|
||||||
.setFlowId(flowId)
|
|
||||||
.setBrowserFlow(true)
|
|
||||||
.setConnection(clientConnection)
|
|
||||||
.setEventBuilder(event)
|
|
||||||
.setProtector(authManager.getProtector())
|
|
||||||
.setRealm(realm)
|
|
||||||
.setSession(session)
|
|
||||||
.setUriInfo(uriInfo)
|
|
||||||
.setRequest(request);
|
|
||||||
|
|
||||||
try {
|
|
||||||
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
|
|
||||||
return processor.authenticate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return processor.handleBrowserException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
|
||||||
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
|
||||||
logger.debug("SAML GET");
|
logger.debug("SAML GET");
|
||||||
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||||
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
|
||||||
@FormParam(GeneralConstants.RELAY_STATE) String relayState) {
|
|
||||||
logger.debug("SAML POST");
|
logger.debug("SAML POST");
|
||||||
return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
|
||||||
}
|
}
|
||||||
|
@ -570,13 +481,13 @@ public class SamlService {
|
||||||
@GET
|
@GET
|
||||||
@Path("clients/{client}")
|
@Path("clients/{client}")
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName,
|
public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
|
||||||
@QueryParam("RelayState") String relayState) {
|
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
ClientModel client = null;
|
ClientModel client = null;
|
||||||
for (ClientModel c : realm.getClients()) {
|
for (ClientModel c : realm.getClients()) {
|
||||||
String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
|
String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
|
||||||
if (urlName == null) continue;
|
if (urlName == null)
|
||||||
|
continue;
|
||||||
if (urlName.equals(clientUrlName)) {
|
if (urlName.equals(clientUrlName)) {
|
||||||
client = c;
|
client = c;
|
||||||
break;
|
break;
|
||||||
|
@ -586,18 +497,14 @@ public class SamlService {
|
||||||
event.error(Errors.CLIENT_NOT_FOUND);
|
event.error(Errors.CLIENT_NOT_FOUND);
|
||||||
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
|
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (client.getManagementUrl() == null
|
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
|
||||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
|
|
||||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
|
|
||||||
logger.error("SAML assertion consumer url not set up");
|
logger.error("SAML assertion consumer url not set up");
|
||||||
event.error(Errors.INVALID_REDIRECT_URI);
|
event.error(Errors.INVALID_REDIRECT_URI);
|
||||||
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
String bindingType = SamlProtocol.SAML_POST_BINDING;
|
String bindingType = SamlProtocol.SAML_POST_BINDING;
|
||||||
if (client.getManagementUrl() == null
|
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
|
||||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
|
|
||||||
&& client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
|
|
||||||
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
|
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,8 +533,7 @@ public class SamlService {
|
||||||
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newBrowserAuthentication(clientSession, false);
|
||||||
return newBrowserAuthentication(clientSession);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
import org.keycloak.protocol.LoginProtocol.Error;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
@ -470,7 +471,7 @@ public class AuthenticationProcessor {
|
||||||
protocol.setRealm(getRealm())
|
protocol.setRealm(getRealm())
|
||||||
.setHttpHeaders(getHttpRequest().getHttpHeaders())
|
.setHttpHeaders(getHttpRequest().getHttpHeaders())
|
||||||
.setUriInfo(getUriInfo());
|
.setUriInfo(getUriInfo());
|
||||||
Response response = protocol.cancelLogin(getClientSession());
|
Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
|
||||||
forceChallenge(response);
|
forceChallenge(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
package org.keycloak.protocol;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.common.ClientConnection;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
|
import org.keycloak.models.ClientSessionModel;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.protocol.LoginProtocol.Error;
|
||||||
|
import org.keycloak.services.Urls;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common base class for Authorization REST endpoints implementation, which have to be implemented by each protocol.
|
||||||
|
*
|
||||||
|
* @author Vlastimil Elias (velias at redhat dot com)
|
||||||
|
*/
|
||||||
|
public abstract class AuthorizationEndpointBase {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AuthorizationEndpointBase.class);
|
||||||
|
|
||||||
|
protected RealmModel realm;
|
||||||
|
protected EventBuilder event;
|
||||||
|
protected AuthenticationManager authManager;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected UriInfo uriInfo;
|
||||||
|
@Context
|
||||||
|
protected HttpHeaders headers;
|
||||||
|
@Context
|
||||||
|
protected HttpRequest request;
|
||||||
|
@Context
|
||||||
|
protected KeycloakSession session;
|
||||||
|
@Context
|
||||||
|
protected ClientConnection clientConnection;
|
||||||
|
|
||||||
|
public AuthorizationEndpointBase(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.event = event;
|
||||||
|
this.authManager = authManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AuthenticationProcessor createProcessor(ClientSessionModel clientSession, String flowId, String flowPath) {
|
||||||
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
|
processor.setClientSession(clientSession)
|
||||||
|
.setFlowPath(flowPath)
|
||||||
|
.setFlowId(flowId)
|
||||||
|
.setBrowserFlow(true)
|
||||||
|
.setConnection(clientConnection)
|
||||||
|
.setEventBuilder(event)
|
||||||
|
.setProtector(authManager.getProtector())
|
||||||
|
.setRealm(realm)
|
||||||
|
.setSession(session)
|
||||||
|
.setUriInfo(uriInfo)
|
||||||
|
.setRequest(request);
|
||||||
|
return processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common method to handle browser authentication request in protocols unified way.
|
||||||
|
*
|
||||||
|
* @param clientSession for current request
|
||||||
|
* @param protocol handler for protocol used to initiate login
|
||||||
|
* @param isPassive set to true if login should be passive (without login screen shown)
|
||||||
|
* @return response to be returned to the browser
|
||||||
|
*/
|
||||||
|
protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive) {
|
||||||
|
|
||||||
|
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
||||||
|
for (IdentityProviderModel identityProvider : identityProviders) {
|
||||||
|
if (identityProvider.isAuthenticateByDefault()) {
|
||||||
|
// TODO if we are isPassive we should propagate this flag to default identity provider also if possible
|
||||||
|
return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationFlowModel flow = realm.getBrowserFlow();
|
||||||
|
String flowId = flow.getId();
|
||||||
|
AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
|
||||||
|
|
||||||
|
if (isPassive) {
|
||||||
|
// OIDC prompt == NONE or SAML 2 IsPassive flag
|
||||||
|
// This means that client is just checking if the user is already completely logged in.
|
||||||
|
// We cancel login if any authentication action or required action is required
|
||||||
|
Response challenge = null;
|
||||||
|
Response challenge2 = null;
|
||||||
|
try {
|
||||||
|
challenge = processor.authenticateOnly();
|
||||||
|
if (challenge == null) {
|
||||||
|
challenge2 = processor.attachSessionExecutionRequiredActions();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return processor.handleBrowserException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (challenge != null || challenge2 != null) {
|
||||||
|
if (processor.isUserSessionCreated()) {
|
||||||
|
session.sessions().removeUserSession(realm, processor.getUserSession());
|
||||||
|
}
|
||||||
|
if (challenge != null)
|
||||||
|
return protocol.sendError(clientSession, Error.PASSIVE_LOGIN_REQUIRED);
|
||||||
|
else
|
||||||
|
return protocol.sendError(clientSession, Error.PASSIVE_INTERACTION_REQUIRED);
|
||||||
|
} else {
|
||||||
|
return processor.finishAuthentication();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
|
||||||
|
return processor.authenticate();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return processor.handleBrowserException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
||||||
|
logger.debug("Automatically redirect to identity provider: " + providerId);
|
||||||
|
return Response.temporaryRedirect(
|
||||||
|
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,28 @@ import javax.ws.rs.core.UriInfo;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public interface LoginProtocol extends Provider {
|
public interface LoginProtocol extends Provider {
|
||||||
|
|
||||||
|
public static enum Error {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login cancelled by the user
|
||||||
|
*/
|
||||||
|
CANCELLED_BY_USER,
|
||||||
|
/**
|
||||||
|
* Consent denied by the user
|
||||||
|
*/
|
||||||
|
CONSENT_DENIED,
|
||||||
|
/**
|
||||||
|
* Passive authentication mode requested but nobody is logged in
|
||||||
|
*/
|
||||||
|
PASSIVE_LOGIN_REQUIRED,
|
||||||
|
/**
|
||||||
|
* Passive authentication mode requested, user is logged in, but some other user interaction is necessary (eg. some required login actions exist or Consent approval is necessary for logged in
|
||||||
|
* user)
|
||||||
|
*/
|
||||||
|
PASSIVE_INTERACTION_REQUIRED;
|
||||||
|
}
|
||||||
|
|
||||||
LoginProtocol setSession(KeycloakSession session);
|
LoginProtocol setSession(KeycloakSession session);
|
||||||
|
|
||||||
LoginProtocol setRealm(RealmModel realm);
|
LoginProtocol setRealm(RealmModel realm);
|
||||||
|
@ -27,11 +49,12 @@ public interface LoginProtocol extends Provider {
|
||||||
|
|
||||||
LoginProtocol setEventBuilder(EventBuilder event);
|
LoginProtocol setEventBuilder(EventBuilder event);
|
||||||
|
|
||||||
Response cancelLogin(ClientSessionModel clientSession);
|
|
||||||
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
||||||
Response consentDenied(ClientSessionModel clientSession);
|
|
||||||
|
Response sendError(ClientSessionModel clientSession, Error error);
|
||||||
|
|
||||||
void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
|
void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
|
||||||
Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
|
Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
|
||||||
Response finishLogout(UserSessionModel userSession);
|
Response finishLogout(UserSessionModel userSession);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OIDCLoginProtocol(){
|
public OIDCLoginProtocol() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){
|
public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers) {
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -116,19 +116,6 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
|
||||||
String redirect = clientSession.getRedirectUri();
|
|
||||||
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
|
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
|
||||||
if (state != null) {
|
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
|
||||||
}
|
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
|
||||||
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
|
|
||||||
return Response.status(302).location(redirectUri.build()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
|
||||||
ClientSessionModel clientSession = accessCode.getClientSession();
|
ClientSessionModel clientSession = accessCode.getClientSession();
|
||||||
|
@ -144,10 +131,11 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
return location.build();
|
return location.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Response consentDenied(ClientSessionModel clientSession) {
|
@Override
|
||||||
|
public Response sendError(ClientSessionModel clientSession, Error error) {
|
||||||
String redirect = clientSession.getRedirectUri();
|
String redirect = clientSession.getRedirectUri();
|
||||||
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
|
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, translateError(error));
|
||||||
if (state != null)
|
if (state != null)
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
||||||
session.sessions().removeClientSession(realm, clientSession);
|
session.sessions().removeClientSession(realm, clientSession);
|
||||||
|
@ -156,20 +144,25 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
return location.build();
|
return location.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String translateError(Error error) {
|
||||||
public Response invalidSessionError(ClientSessionModel clientSession) {
|
switch (error) {
|
||||||
String redirect = clientSession.getRedirectUri();
|
case CANCELLED_BY_USER:
|
||||||
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
|
case CONSENT_DENIED:
|
||||||
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, "access_denied");
|
return "access_denied";
|
||||||
if (state != null) {
|
case PASSIVE_INTERACTION_REQUIRED:
|
||||||
redirectUri.queryParam(OAuth2Constants.STATE, state);
|
return "interaction_required";
|
||||||
|
case PASSIVE_LOGIN_REQUIRED:
|
||||||
|
return "login_required";
|
||||||
|
default:
|
||||||
|
log.warn("Untranslated protocol Error: " + error.name() + " so we return default SAML error");
|
||||||
|
return "access_denied";
|
||||||
}
|
}
|
||||||
return Response.status(302).location(redirectUri.build()).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
|
||||||
if (!(clientSession.getClient() instanceof ClientModel)) return;
|
if (!(clientSession.getClient() instanceof ClientModel))
|
||||||
|
return;
|
||||||
ClientModel app = clientSession.getClient();
|
ClientModel app = clientSession.getClient();
|
||||||
new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
|
new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
|
||||||
}
|
}
|
||||||
|
@ -190,10 +183,10 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
}
|
}
|
||||||
event.user(userSession.getUser()).session(userSession).success();
|
event.user(userSession.getUser()).session(userSession).success();
|
||||||
|
|
||||||
|
|
||||||
if (redirectUri != null) {
|
if (redirectUri != null) {
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(redirectUri);
|
UriBuilder uriBuilder = UriBuilder.fromUri(redirectUri);
|
||||||
if (state != null) uriBuilder.queryParam(STATE_PARAM, state);
|
if (state != null)
|
||||||
|
uriBuilder.queryParam(STATE_PARAM, state);
|
||||||
return Response.status(302).location(uriBuilder.build()).build();
|
return Response.status(302).location(uriBuilder.build()).build();
|
||||||
} else {
|
} else {
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
package org.keycloak.protocol.oidc.endpoints;
|
package org.keycloak.protocol.oidc.endpoints;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
import org.keycloak.common.ClientConnection;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.AuthenticationProcessor;
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -18,6 +27,7 @@ import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.RestartLoginCookie;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
|
@ -28,45 +38,19 @@ import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class AuthorizationEndpoint {
|
public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
|
private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
|
||||||
|
|
||||||
public static final String CODE_AUTH_TYPE = "code";
|
public static final String CODE_AUTH_TYPE = "code";
|
||||||
|
|
||||||
private enum Action {
|
private enum Action {
|
||||||
REGISTER, CODE, FORGOT_CREDENTIALS
|
REGISTER, CODE, FORGOT_CREDENTIALS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Context
|
|
||||||
private KeycloakSession session;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private HttpRequest request;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private HttpHeaders headers;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private UriInfo uriInfo;
|
|
||||||
|
|
||||||
@Context
|
|
||||||
private ClientConnection clientConnection;
|
|
||||||
|
|
||||||
private final AuthenticationManager authManager;
|
|
||||||
private final RealmModel realm;
|
|
||||||
private final EventBuilder event;
|
|
||||||
|
|
||||||
private ClientModel client;
|
private ClientModel client;
|
||||||
private ClientSessionModel clientSession;
|
private ClientSessionModel clientSession;
|
||||||
|
|
||||||
|
@ -86,9 +70,7 @@ public class AuthorizationEndpoint {
|
||||||
private String legacyResponseType;
|
private String legacyResponseType;
|
||||||
|
|
||||||
public AuthorizationEndpoint(AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
|
public AuthorizationEndpoint(AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
|
||||||
this.authManager = authManager;
|
super(realm, event, authManager);
|
||||||
this.realm = realm;
|
|
||||||
this.event = event;
|
|
||||||
event.event(EventType.LOGIN);
|
event.event(EventType.LOGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +231,6 @@ public class AuthorizationEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response buildAuthorizationCodeAuthorizationResponse() {
|
private Response buildAuthorizationCodeAuthorizationResponse() {
|
||||||
String accessCode = new ClientSessionCode(realm, clientSession).getCode();
|
|
||||||
|
|
||||||
if (idpHint != null && !"".equals(idpHint)) {
|
if (idpHint != null && !"".equals(idpHint)) {
|
||||||
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
|
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
|
||||||
|
@ -259,65 +240,13 @@ public class AuthorizationEndpoint {
|
||||||
.setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
|
.setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
|
||||||
.createErrorPage();
|
.createErrorPage();
|
||||||
}
|
}
|
||||||
return buildRedirectToIdentityProvider(idpHint, accessCode);
|
return buildRedirectToIdentityProvider(idpHint, new ClientSessionCode(realm, clientSession).getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
return browserAuthentication(accessCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Response browserAuthentication(String accessCode) {
|
|
||||||
this.event.event(EventType.LOGIN);
|
this.event.event(EventType.LOGIN);
|
||||||
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
|
|
||||||
for (IdentityProviderModel identityProvider : identityProviders) {
|
|
||||||
if (identityProvider.isAuthenticateByDefault()) {
|
|
||||||
return buildRedirectToIdentityProvider(identityProvider.getAlias(), accessCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
|
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
|
||||||
|
|
||||||
|
return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), prompt != null && prompt.equals("none"));
|
||||||
AuthenticationFlowModel flow = realm.getBrowserFlow();
|
|
||||||
String flowId = flow.getId();
|
|
||||||
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.AUTHENTICATE_PATH);
|
|
||||||
|
|
||||||
if (prompt != null && prompt.equals("none")) {
|
|
||||||
// OIDC prompt == NONE
|
|
||||||
// This means that client is just checking if the user is already completely logged in.
|
|
||||||
//
|
|
||||||
// here we cancel login if any authentication action or required action is required
|
|
||||||
Response challenge = null;
|
|
||||||
try {
|
|
||||||
challenge = processor.authenticateOnly();
|
|
||||||
if (challenge == null) {
|
|
||||||
challenge = processor.attachSessionExecutionRequiredActions();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
return processor.handleBrowserException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (challenge != null) {
|
|
||||||
if (processor.isUserSessionCreated()) {
|
|
||||||
session.sessions().removeUserSession(realm, processor.getUserSession());
|
|
||||||
}
|
|
||||||
OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
|
|
||||||
return oauth.cancelLogin(clientSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (challenge == null) {
|
|
||||||
return processor.finishAuthentication();
|
|
||||||
} else {
|
|
||||||
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
RestartLoginCookie.setRestartCookie(realm, clientConnection, uriInfo, clientSession);
|
|
||||||
return processor.authenticate();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return processor.handleBrowserException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response buildRegister() {
|
private Response buildRegister() {
|
||||||
|
@ -326,7 +255,7 @@ public class AuthorizationEndpoint {
|
||||||
AuthenticationFlowModel flow = realm.getRegistrationFlow();
|
AuthenticationFlowModel flow = realm.getRegistrationFlow();
|
||||||
String flowId = flow.getId();
|
String flowId = flow.getId();
|
||||||
|
|
||||||
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.REGISTRATION_PATH);
|
AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.REGISTRATION_PATH);
|
||||||
|
|
||||||
return processor.authenticate();
|
return processor.authenticate();
|
||||||
}
|
}
|
||||||
|
@ -337,32 +266,12 @@ public class AuthorizationEndpoint {
|
||||||
AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
|
AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
|
||||||
String flowId = flow.getId();
|
String flowId = flow.getId();
|
||||||
|
|
||||||
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.RESET_CREDENTIALS_PATH);
|
AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.RESET_CREDENTIALS_PATH);
|
||||||
|
|
||||||
return processor.authenticate();
|
return processor.authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthenticationProcessor createProcessor(String flowId, String flowPath) {
|
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
|
||||||
processor.setClientSession(clientSession)
|
|
||||||
.setFlowPath(flowPath)
|
|
||||||
.setFlowId(flowId)
|
|
||||||
.setBrowserFlow(true)
|
|
||||||
.setConnection(clientConnection)
|
|
||||||
.setEventBuilder(event)
|
|
||||||
.setProtector(authManager.getProtector())
|
|
||||||
.setRealm(realm)
|
|
||||||
.setSession(session)
|
|
||||||
.setUriInfo(uriInfo)
|
|
||||||
.setRequest(request);
|
|
||||||
return processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
|
|
||||||
logger.debug("Automatically redirect to identity provider: " + providerId);
|
|
||||||
return Response.temporaryRedirect(
|
|
||||||
Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -36,6 +36,7 @@ import org.keycloak.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.*;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
import org.keycloak.protocol.LoginProtocol.Error;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.RestartLoginCookie;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
|
@ -523,7 +524,7 @@ public class AuthenticationManager {
|
||||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||||
.setUriInfo(context.getUriInfo());
|
.setUriInfo(context.getUriInfo());
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
return protocol.consentDenied(context.getClientSession());
|
return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
|
||||||
}
|
}
|
||||||
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
|
||||||
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
|
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
|
||||||
|
|
|
@ -57,6 +57,7 @@ import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.RestartLoginCookie;
|
import org.keycloak.protocol.RestartLoginCookie;
|
||||||
|
import org.keycloak.protocol.LoginProtocol.Error;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
@ -591,7 +592,7 @@ public class LoginActionsService {
|
||||||
.setHttpHeaders(headers)
|
.setHttpHeaders(headers)
|
||||||
.setUriInfo(uriInfo);
|
.setUriInfo(uriInfo);
|
||||||
event.error(Errors.REJECTED_BY_USER);
|
event.error(Errors.REJECTED_BY_USER);
|
||||||
return protocol.consentDenied(clientSession);
|
return protocol.sendError(clientSession, Error.CONSENT_DENIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
|
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
|
||||||
|
@ -828,7 +829,7 @@ public class LoginActionsService {
|
||||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||||
.setUriInfo(context.getUriInfo());
|
.setUriInfo(context.getUriInfo());
|
||||||
event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
|
event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
|
||||||
return protocol.consentDenied(context.getClientSession());
|
return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException("Unreachable");
|
throw new RuntimeException("Unreachable");
|
||||||
|
|
Loading…
Reference in a new issue