Details
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 8d6fa15..6729c90 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -1,5 +1,22 @@
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.HttpResponse;
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.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
-import org.keycloak.services.ErrorPage;
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>
* @version $Revision: 1 $
@@ -67,7 +68,6 @@ import java.util.UUID;
public class SamlProtocol implements LoginProtocol {
protected static final Logger logger = Logger.getLogger(SamlProtocol.class);
-
public static final String ATTRIBUTE_TRUE_VALUE = "true";
public static final String ATTRIBUTE_FALSE_VALUE = "false";
public static final String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
@@ -115,7 +115,6 @@ public class SamlProtocol implements LoginProtocol {
protected EventBuilder event;
-
@Override
public SamlProtocol setSession(KeycloakSession session) {
this.session = session;
@@ -135,7 +134,7 @@ public class SamlProtocol implements LoginProtocol {
}
@Override
- public SamlProtocol setHttpHeaders(HttpHeaders headers){
+ public SamlProtocol setHttpHeaders(HttpHeaders headers) {
this.headers = headers;
return this;
}
@@ -146,48 +145,69 @@ public class SamlProtocol implements LoginProtocol {
return this;
}
-
@Override
- public Response cancelLogin(ClientSessionModel clientSession) {
+ public Response sendError(ClientSessionModel clientSession, Error error) {
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
+ session.sessions().removeClientSession(realm, clientSession);
if ("true".equals(clientSession.getClient().getAttribute(SAML_IDP_INITIATED_LOGIN))) {
- UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
- Map<String, String> params = new HashMap<>();
- params.put("realm", realm.getName());
- params.put("protocol", LOGIN_PROTOCOL);
- params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
- session.sessions().removeClientSession(realm, clientSession);
- URI redirect = builder.buildFromMap(params);
- return Response.status(302).location(redirect).build();
+ if (error == Error.CANCELLED_BY_USER) {
+ UriBuilder builder = RealmsResource.protocolUrl(uriInfo).path(SamlService.class, "idpInitiatedSSO");
+ Map<String, String> params = new HashMap<>();
+ params.put("realm", realm.getName());
+ params.put("protocol", LOGIN_PROTOCOL);
+ params.put("client", clientSession.getClient().getAttribute(SAML_IDP_INITIATED_SSO_URL_NAME));
+ URI redirect = builder.buildFromMap(params);
+ return Response.status(302).location(redirect).build();
+ } else {
+ return ErrorPage.error(session, translateErrorToIdpInitiatedErrorMessage(error));
+ }
} else {
- session.sessions().removeClientSession(realm, clientSession);
- return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
+ SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder().destination(clientSession.getRedirectUri()).issuer(getResponseIssuer(realm)).status(translateErrorToSAMLStatus(error).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);
+ }
}
}
- protected String getResponseIssuer(RealmModel realm) {
- return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
+ 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;
+ }
}
- 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);
+ 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;
}
}
+ protected String getResponseIssuer(RealmModel realm) {
+ return RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString();
+ }
+
protected boolean isPostBinding(ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
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);
}
-
-
protected boolean isLogoutPostBindingForClient(ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
String logoutPostUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
@@ -207,7 +225,8 @@ public class SamlProtocol implements LoginProtocol {
if (logoutPostUrl == null) {
// 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;
}
@@ -218,11 +237,13 @@ public class SamlProtocol implements LoginProtocol {
String bindingType = clientSession.getNote(SAML_BINDING);
// 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();
}
}
- if(nameIdFormat == null) return SAML_DEFAULT_NAMEID_FORMAT;
+ if (nameIdFormat == null)
+ return SAML_DEFAULT_NAMEID_FORMAT;
return nameIdFormat;
}
@@ -259,20 +281,21 @@ public class SamlProtocol implements LoginProtocol {
protected String getNameId(String nameIdFormat, ClientSessionModel clientSession, UserSessionModel userSession) {
if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
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.
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.
UserModel user = userSession.getUser();
String name = SAML_PERSISTENT_NAME_ID_FOR + "." + clientSession.getClient().getClientId();
String samlPersistentId = user.getFirstAttribute(name);
- if (samlPersistentId != null) return samlPersistentId;
+ if (samlPersistentId != null)
+ return samlPersistentId;
// "G-" stands for "generated"
samlPersistentId = "G-" + UUID.randomUUID().toString();
user.setSingleAttribute(name, 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)
return userSession.getUser().getUsername();
} else {
@@ -297,15 +320,8 @@ public class SamlProtocol implements LoginProtocol {
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
- builder.requestID(requestID)
- .destination(redirectUri)
- .issuer(responseIssuer)
- .assertionExpiration(realm.getAccessCodeLifespan())
- .subjectExpiration(realm.getAccessTokenLifespan())
- .sessionIndex(clientSession.getId())
- .requestIssuer(clientSession.getClient().getClientId())
- .nameIdentifier(nameIdFormat, nameId)
- .authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
+ builder.requestID(requestID).destination(redirectUri).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)) {
builder.disableAuthnStatement(true);
}
@@ -317,20 +333,20 @@ public class SamlProtocol implements LoginProtocol {
Set<ProtocolMapperModel> mappings = accessCode.getRequestedProtocolMappers();
for (ProtocolMapperModel mapping : mappings) {
- ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
- if (mapper == null) continue;
+ ProtocolMapper mapper = (ProtocolMapper) session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+ if (mapper == null)
+ continue;
if (mapper instanceof SAMLAttributeStatementMapper) {
- attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper)mapper, mapping));
+ attributeStatementMappers.add(new ProtocolMapperProcessor<SAMLAttributeStatementMapper>((SAMLAttributeStatementMapper) mapper, mapping));
}
if (mapper instanceof SAMLLoginResponseMapper) {
- loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper)mapper, mapping));
+ loginResponseMappers.add(new ProtocolMapperProcessor<SAMLLoginResponseMapper>((SAMLLoginResponseMapper) mapper, mapping));
}
if (mapper instanceof SAMLRoleListMapper) {
- roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper)mapper, mapping);
+ roleListMapper = new ProtocolMapperProcessor<SAMLRoleListMapper>((SAMLRoleListMapper) mapper, mapping);
}
}
-
Document samlDocument = null;
try {
ResponseType samlModel = builder.buildModel();
@@ -351,18 +367,14 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
- .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signDocument();
+ bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
}
if (requiresAssertionSignature(client)) {
String canonicalization = client.getAttribute(SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
- bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client))
- .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signAssertions();
+ bindingBuilder.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signAssertions();
}
if (requiresEncryption(client)) {
PublicKey publicKey = null;
@@ -402,7 +414,8 @@ public class SamlProtocol implements LoginProtocol {
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
if (alg != null) {
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
- if (algorithm != null) return algorithm;
+ if (algorithm != null)
+ return algorithm;
}
return SignatureAlgorithm.RSA_SHA256;
}
@@ -421,10 +434,8 @@ public class SamlProtocol implements LoginProtocol {
}
}
- public void transformAttributeStatement(List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers,
- ResponseType response,
- KeycloakSession session,
- UserSessionModel userSession, ClientSessionModel clientSession) {
+ public void transformAttributeStatement(List<ProtocolMapperProcessor<SAMLAttributeStatementMapper>> attributeStatementMappers, ResponseType response, KeycloakSession session, UserSessionModel userSession,
+ ClientSessionModel clientSession) {
AssertionType assertion = response.getAssertions().get(0).getAssertion();
AttributeStatementType attributeStatement = new AttributeStatementType();
@@ -432,50 +443,32 @@ public class SamlProtocol implements LoginProtocol {
processor.mapper.transformAttributeStatement(attributeStatement, processor.model, session, userSession, clientSession);
}
- //SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
- if(attributeStatement.getAttributes().size() > 0) {
+ // SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
+ if (attributeStatement.getAttributes().size() > 0) {
assertion.addStatement(attributeStatement);
}
}
- public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers,
- ResponseType response,
- KeycloakSession session,
- UserSessionModel userSession, ClientSessionModel clientSession) {
+ public ResponseType transformLoginResponse(List<ProtocolMapperProcessor<SAMLLoginResponseMapper>> mappers, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
for (ProtocolMapperProcessor<SAMLLoginResponseMapper> processor : mappers) {
response = processor.mapper.transformLoginResponse(response, processor.model, session, userSession, clientSession);
}
return response;
}
- public void populateRoles(ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper,
- ResponseType response,
- KeycloakSession session,
- UserSessionModel userSession, ClientSessionModel clientSession) {
- if (roleListMapper == null) return;
+ public void populateRoles(ProtocolMapperProcessor<SAMLRoleListMapper> roleListMapper, ResponseType response, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ if (roleListMapper == null)
+ return;
AssertionType assertion = response.getAssertions().get(0).getAssertion();
AttributeStatementType attributeStatement = new AttributeStatementType();
roleListMapper.mapper.mapRoles(attributeStatement, roleListMapper.model, session, userSession, clientSession);
- //SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
- if(attributeStatement.getAttributes().size() > 0) {
+ // SAML Spec 2.7.3 AttributeStatement must contain one or more Attribute or EncryptedAttribute
+ if (attributeStatement.getAttributes().size() > 0) {
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) {
String logoutServiceUrl = null;
if (SAML_POST_BINDING.equals(bindingType)) {
@@ -483,8 +476,10 @@ public class SamlProtocol implements LoginProtocol {
} else {
logoutServiceUrl = client.getAttribute(SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE);
}
- if (logoutServiceUrl == null && client instanceof ClientModel) logoutServiceUrl = ((ClientModel)client).getManagementUrl();
- if (logoutServiceUrl == null || logoutServiceUrl.trim().equals("")) return null;
+ if (logoutServiceUrl == null && client instanceof ClientModel)
+ logoutServiceUrl = ((ClientModel) client).getManagementUrl();
+ if (logoutServiceUrl == null || logoutServiceUrl.trim().equals(""))
+ return null;
return ResourceAdminManager.resolveUri(uriInfo.getRequestUri(), client.getRootUrl(), logoutServiceUrl);
}
@@ -492,7 +487,8 @@ public class SamlProtocol implements LoginProtocol {
@Override
public Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
- if (!(client instanceof ClientModel)) return null;
+ if (!(client instanceof ClientModel))
+ return null;
try {
if (isLogoutPostBindingForClient(clientSession)) {
String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
@@ -541,9 +537,7 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) {
binding.canonicalizationMethod(canonicalization);
}
- binding.signatureAlgorithm(algorithm)
- .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signDocument();
+ binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
}
try {
@@ -561,8 +555,6 @@ public class SamlProtocol implements LoginProtocol {
}
}
-
-
@Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
ClientModel client = clientSession.getClient();
@@ -573,7 +565,6 @@ public class SamlProtocol implements LoginProtocol {
}
SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(logoutUrl, clientSession, client);
-
String logoutRequestString = null;
try {
JaxrsSAML2BindingBuilder binding = createBindingBuilder(client);
@@ -583,20 +574,21 @@ public class SamlProtocol implements LoginProtocol {
return;
}
-
HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient();
for (int i = 0; i < 2; i++) { // follow redirects once
try {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
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");
HttpPost post = new HttpPost(logoutUrl);
post.setEntity(form);
HttpResponse response = httpClient.execute(post);
try {
int status = response.getStatusLine().getStatusCode();
- if (status == 302 && !logoutUrl.endsWith("/")) {
+ if (status == 302 && !logoutUrl.endsWith("/")) {
String redirect = response.getFirstHeader(HttpHeaders.LOCATION).getValue();
String withSlash = logoutUrl + "/";
if (withSlash.equals(redirect)) {
@@ -608,7 +600,8 @@ public class SamlProtocol implements LoginProtocol {
HttpEntity entity = response.getEntity();
if (entity != null) {
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) {
// build userPrincipal with subject used at login
- SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
- .assertionExpiration(realm.getAccessCodeLifespan())
- .issuer(getResponseIssuer(realm))
- .sessionIndex(clientSession.getId())
- .userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT))
- .destination(logoutUrl);
+ SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)).sessionIndex(clientSession.getId())
+ .userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl);
return logoutBuilder;
}
private JaxrsSAML2BindingBuilder createBindingBuilder(ClientModel client) {
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
if (requiresRealmSignature(client)) {
- binding.signatureAlgorithm(getSignatureAlgorithm(client))
- .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signDocument();
+ binding.signatureAlgorithm(getSignatureAlgorithm(client)).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
}
return binding;
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index c370d67..ff1275a 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -1,11 +1,23 @@
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.resteasy.spi.HttpRequest;
-import org.jboss.resteasy.spi.HttpResponse;
-import org.keycloak.common.ClientConnection;
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.protocol.AuthnRequestType;
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.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
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.saml.SAML2LogoutResponseBuilder;
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.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.services.ErrorPage;
-import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
-import org.keycloak.services.resources.LoginActionsService;
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
@@ -67,40 +53,12 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class SamlService {
+public class SamlService extends AuthorizationEndpointBase {
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) {
- this.realm = realm;
- this.event = event;
- this.authManager = authManager;
+ super(realm, event, authManager);
}
public abstract class BindingProtocol {
@@ -243,7 +201,7 @@ public class SamlService {
bindingType = SamlProtocol.SAML_POST_BINDING;
String redirect = null;
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);
} else {
if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
@@ -262,7 +220,6 @@ public class SamlService {
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
-
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirect);
@@ -286,13 +243,9 @@ public class SamlService {
}
}
- return newBrowserAuthentication(clientSession);
+ return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive());
}
-
-
-
-
private String getBindingType(AuthnRequestType requestAbstractType) {
URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
@@ -308,10 +261,8 @@ public class SamlService {
}
private boolean isSupportedNameIdFormat(String nameIdFormat) {
- 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_UNSPECIFIED.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_UNSPECIFIED.get())) {
return true;
}
return false;
@@ -340,7 +291,8 @@ public class SamlService {
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_BINDING, logoutBinding);
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) {
for (String sessionIndex : logoutRequest.getSessionIndex()) {
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
- if (clientSession == null) continue;
+ if (clientSession == null)
+ continue;
UserSessionModel userSession = clientSession.getUserSession();
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
// remove requesting client from logout
@@ -391,13 +344,10 @@ public class SamlService {
builder.logoutRequestID(logoutRequest.getID());
builder.destination(logoutBindingUri);
builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
- JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
- .relayState(logoutRelayState);
+ JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
if (SamlProtocol.requiresRealmSignature(client)) {
SignatureAlgorithm algorithm = SamlProtocol.getSignatureAlgorithm(client);
- binding.signatureAlgorithm(algorithm)
- .signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
- .signDocument();
+ binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
}
try {
@@ -420,7 +370,6 @@ public class SamlService {
}
}
-
protected class PostBindingProtocol extends BindingProtocol {
@Override
@@ -443,12 +392,14 @@ public class SamlService {
return SamlProtocol.SAML_POST_BINDING;
}
-
public Response execute(String samlRequest, String samlResponse, String relayState) {
Response response = basicChecks(samlRequest, samlResponse);
- if (response != null) return response;
- if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
- else return handleSamlResponse(samlResponse, relayState);
+ if (response != null)
+ return response;
+ 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);
}
-
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
@@ -480,74 +430,35 @@ public class SamlService {
return SamlProtocol.SAML_REDIRECT_BINDING;
}
-
public Response execute(String samlRequest, String samlResponse, String relayState) {
Response response = basicChecks(samlRequest, samlResponse);
- if (response != null) return response;
- if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
- else return handleSamlResponse(samlResponse, relayState);
+ if (response != null)
+ return response;
+ if (samlRequest != null)
+ return handleSamlRequest(samlRequest, relayState);
+ else
+ return handleSamlResponse(samlResponse, relayState);
}
}
-
- private Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
- 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);
- }
+ protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) {
+ return handleBrowserAuthenticationRequest(clientSession, new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo), isPassive);
}
-
-
/**
*/
@GET
- public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
- @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
- @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
+ public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
logger.debug("SAML GET");
return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
}
-
/**
*/
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
- @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
- @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
+ public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
logger.debug("SAML POST");
return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
}
@@ -570,13 +481,13 @@ public class SamlService {
@GET
@Path("clients/{client}")
@Produces(MediaType.TEXT_HTML)
- public Response idpInitiatedSSO(@PathParam("client") String clientUrlName,
- @QueryParam("RelayState") String relayState) {
+ public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
event.event(EventType.LOGIN);
ClientModel client = null;
for (ClientModel c : realm.getClients()) {
String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
- if (urlName == null) continue;
+ if (urlName == null)
+ continue;
if (urlName.equals(clientUrlName)) {
client = c;
break;
@@ -586,18 +497,14 @@ public class SamlService {
event.error(Errors.CLIENT_NOT_FOUND);
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
}
- if (client.getManagementUrl() == null
- && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
- && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == 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) {
logger.error("SAML assertion consumer url not set up");
event.error(Errors.INVALID_REDIRECT_URI);
return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
}
String bindingType = SamlProtocol.SAML_POST_BINDING;
- if (client.getManagementUrl() == null
- && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null
- && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != 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) {
bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
}
@@ -626,8 +533,7 @@ public class SamlService {
clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
}
-
- return newBrowserAuthentication(clientSession);
+ return newBrowserAuthentication(clientSession, false);
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 951bb94..dafaf1a 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -21,6 +21,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
@@ -470,7 +471,7 @@ public class AuthenticationProcessor {
protocol.setRealm(getRealm())
.setHttpHeaders(getHttpRequest().getHttpHeaders())
.setUriInfo(getUriInfo());
- Response response = protocol.cancelLogin(getClientSession());
+ Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
forceChallenge(response);
}
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
new file mode 100644
index 0000000..a1fc4a7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 4a7836d..5cc4503 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -17,6 +17,28 @@ import javax.ws.rs.core.UriInfo;
* @version $Revision: 1 $
*/
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 setRealm(RealmModel realm);
@@ -27,11 +49,12 @@ public interface LoginProtocol extends Provider {
LoginProtocol setEventBuilder(EventBuilder event);
- Response cancelLogin(ClientSessionModel clientSession);
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
- Response consentDenied(ClientSessionModel clientSession);
+
+ Response sendError(ClientSessionModel clientSession, Error error);
void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
Response frontchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession);
Response finishLogout(UserSessionModel userSession);
+
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index e013857..c8d5656 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -1,10 +1,19 @@
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.resteasy.spi.HttpRequest;
-import org.keycloak.common.ClientConnection;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.common.ClientConnection;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -18,6 +27,7 @@ import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
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.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>
*/
-public class AuthorizationEndpoint {
+public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private static final Logger logger = Logger.getLogger(AuthorizationEndpoint.class);
+
public static final String CODE_AUTH_TYPE = "code";
private enum Action {
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 ClientSessionModel clientSession;
@@ -86,9 +70,7 @@ public class AuthorizationEndpoint {
private String legacyResponseType;
public AuthorizationEndpoint(AuthenticationManager authManager, RealmModel realm, EventBuilder event) {
- this.authManager = authManager;
- this.realm = realm;
- this.event = event;
+ super(realm, event, authManager);
event.event(EventType.LOGIN);
}
@@ -249,7 +231,6 @@ public class AuthorizationEndpoint {
}
private Response buildAuthorizationCodeAuthorizationResponse() {
- String accessCode = new ClientSessionCode(realm, clientSession).getCode();
if (idpHint != null && !"".equals(idpHint)) {
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
@@ -259,65 +240,13 @@ public class AuthorizationEndpoint {
.setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
.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);
- 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);
-
- 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);
- }
-
- }
+ return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), prompt != null && prompt.equals("none"));
}
private Response buildRegister() {
@@ -326,7 +255,7 @@ public class AuthorizationEndpoint {
AuthenticationFlowModel flow = realm.getRegistrationFlow();
String flowId = flow.getId();
- AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.REGISTRATION_PATH);
+ AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.REGISTRATION_PATH);
return processor.authenticate();
}
@@ -337,32 +266,12 @@ public class AuthorizationEndpoint {
AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
String flowId = flow.getId();
- AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.RESET_CREDENTIALS_PATH);
+ AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.RESET_CREDENTIALS_PATH);
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();
- }
+
}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 332a547..b9d55db 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -82,7 +82,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
this.event = event;
}
- public OIDCLoginProtocol(){
+ public OIDCLoginProtocol() {
}
@@ -105,7 +105,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
}
@Override
- public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){
+ public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers) {
this.headers = headers;
return this;
}
@@ -117,19 +117,6 @@ public class OIDCLoginProtocol implements LoginProtocol {
}
@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
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
String redirect = clientSession.getRedirectUri();
@@ -144,10 +131,11 @@ public class OIDCLoginProtocol implements LoginProtocol {
return location.build();
}
- public Response consentDenied(ClientSessionModel clientSession) {
+ @Override
+ public Response sendError(ClientSessionModel clientSession, Error error) {
String redirect = clientSession.getRedirectUri();
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)
redirectUri.queryParam(OAuth2Constants.STATE, state);
session.sessions().removeClientSession(realm, clientSession);
@@ -156,20 +144,25 @@ public class OIDCLoginProtocol implements LoginProtocol {
return location.build();
}
-
- public Response invalidSessionError(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);
+ private String translateError(Error error) {
+ switch (error) {
+ case CANCELLED_BY_USER:
+ case CONSENT_DENIED:
+ return "access_denied";
+ case PASSIVE_INTERACTION_REQUIRED:
+ 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
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
- if (!(clientSession.getClient() instanceof ClientModel)) return;
+ if (!(clientSession.getClient() instanceof ClientModel))
+ return;
ClientModel app = clientSession.getClient();
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();
-
if (redirectUri != null) {
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();
} else {
return Response.ok().build();
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index ea47ddc..c820e4d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -36,6 +36,7 @@ import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
@@ -523,7 +524,7 @@ public class AuthenticationManager {
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
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) {
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 7f15d2d..7927991 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -57,6 +57,7 @@ import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls;
@@ -591,7 +592,7 @@ public class LoginActionsService {
.setHttpHeaders(headers)
.setUriInfo(uriInfo);
event.error(Errors.REJECTED_BY_USER);
- return protocol.consentDenied(clientSession);
+ return protocol.sendError(clientSession, Error.CONSENT_DENIED);
}
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
@@ -828,7 +829,7 @@ public class LoginActionsService {
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
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");