broker logout
This commit is contained in:
1 parent
b26277a17c
commit
90c815055d
31 files changed
+823
-126
No files matched your search
@@ -19,6 +19,7 @@ package org.keycloak.broker.provider;
|
|||||||
|
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
@@ -48,4 +49,13 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object callback(RealmModel realm, Callback callback) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -20,16 +20,29 @@ package org.keycloak.broker.provider;
|
|||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
public interface IdentityProvider<C extends IdentityProviderModel> extends Provider {
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JAXRS callback endpoint
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Object callback(RealmModel realm, Callback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
|
* <p>Initiates the authentication process by sending an authentication request to an identity provider. This method is called
|
||||||
* only once during the authentication.</p>
|
* only once during the authentication.</p>
|
||||||
@@ -79,6 +92,8 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||||||
*/
|
*/
|
||||||
Response retrieveToken(FederatedIdentityModel identity);
|
Response retrieveToken(FederatedIdentityModel identity);
|
||||||
|
|
||||||
|
Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
|
* Export a representation of the IdentityProvider in a specific format. For example, a SAML EntityDescriptor
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -31,6 +31,23 @@
|
|||||||
<groupId>org.picketlink</groupId>
|
<groupId>org.picketlink</groupId>
|
||||||
<artifactId>picketlink-federation</artifactId>
|
<artifactId>picketlink-federation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-events-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||||
|
import org.keycloak.broker.provider.FederatedIdentity;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
import org.keycloak.protocol.saml.SAMLRequestParser;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
|
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.managers.EventsManager;
|
||||||
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
|
import org.picketlink.common.constants.GeneralConstants;
|
||||||
|
import org.picketlink.common.constants.JBossSAMLConstants;
|
||||||
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
|
import org.picketlink.common.util.DocumentUtil;
|
||||||
|
import org.picketlink.common.util.StaxParserUtil;
|
||||||
|
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
|
||||||
|
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
|
||||||
|
import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
|
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
|
||||||
|
import org.picketlink.identity.federation.core.util.XMLEncryptionUtil;
|
||||||
|
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.EncryptedAssertionType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.profiles.sso.ecp.RequestType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
|
||||||
|
import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
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.UriInfo;
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class SAMLEndpoint {
|
||||||
|
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
|
||||||
|
protected RealmModel realm;
|
||||||
|
protected EventBuilder event;
|
||||||
|
protected SAMLIdentityProviderConfig config;
|
||||||
|
protected IdentityProvider.Callback callback;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private UriInfo uriInfo;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private ClientConnection clientConnection;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private HttpRequest request;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
private HttpHeaders headers;
|
||||||
|
|
||||||
|
|
||||||
|
public SAMLEndpoint(RealmModel realm, SAMLIdentityProviderConfig config, IdentityProvider.Callback callback) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.config = config;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
|
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
|
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
|
||||||
|
return new RedirectBinding().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) {
|
||||||
|
return new PostBinding().execute(samlRequest, samlResponse, relayState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract class Binding {
|
||||||
|
private boolean checkSsl() {
|
||||||
|
if (uriInfo.getBaseUri().getScheme().equals("https")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return !realm.getSslRequired().isRequired(clientConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response basicChecks(String samlRequest, String samlResponse) {
|
||||||
|
if (!checkSsl()) {
|
||||||
|
event.event(EventType.LOGIN);
|
||||||
|
event.error(Errors.SSL_REQUIRED);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
|
||||||
|
}
|
||||||
|
if (!realm.isEnabled()) {
|
||||||
|
event.event(EventType.LOGIN_ERROR);
|
||||||
|
event.error(Errors.REALM_DISABLED);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samlRequest == null && samlResponse == null) {
|
||||||
|
event.event(EventType.LOGIN);
|
||||||
|
event.error(Errors.INVALID_REQUEST);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getBindingType();
|
||||||
|
protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
|
||||||
|
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
||||||
|
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||||
|
protected PublicKey getIDPKey() {
|
||||||
|
X509Certificate certificate = null;
|
||||||
|
try {
|
||||||
|
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
|
||||||
|
} catch (ProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return certificate.getPublicKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response execute(String samlRequest, String samlResponse, String relayState) {
|
||||||
|
event = new EventsManager(realm, session, clientConnection).createEventBuilder();
|
||||||
|
Response response = basicChecks(samlRequest, samlResponse);
|
||||||
|
if (response != null) return response;
|
||||||
|
if (samlRequest != null) throw new RuntimeException("NOT IMPLEMETED");//return handleSamlRequest(samlRequest, relayState);
|
||||||
|
else return handleSamlResponse(samlResponse, relayState);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
||||||
|
if (config.isValidateSignature()) {
|
||||||
|
try {
|
||||||
|
verifySignature(holder);
|
||||||
|
} catch (VerificationException e) {
|
||||||
|
logger.error("validation failed", e);
|
||||||
|
event.event(EventType.LOGIN);
|
||||||
|
event.error(Errors.INVALID_SIGNATURE);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AssertionType assertion = getAssertion(responseType);
|
||||||
|
SubjectType subject = assertion.getSubject();
|
||||||
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
|
Map<String, String> notes = new HashMap<>();
|
||||||
|
notes.put("SAML_FEDERATED_SUBJECT", subjectNameID.getValue());
|
||||||
|
if (subjectNameID.getFormat() != null) notes.put("SAML_FEDERATED_SUBJECT_NAMEFORMAT", subjectNameID.getFormat().toString());
|
||||||
|
FederatedIdentity identity = new FederatedIdentity(subjectNameID.getValue());
|
||||||
|
|
||||||
|
identity.setUsername(subjectNameID.getValue());
|
||||||
|
|
||||||
|
if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
|
||||||
|
identity.setEmail(subjectNameID.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isStoreToken()) {
|
||||||
|
identity.setToken(samlResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthnStatementType authn = null;
|
||||||
|
for (Object statement : assertion.getStatements()) {
|
||||||
|
if (statement instanceof AuthnStatementType) {
|
||||||
|
authn = (AuthnStatementType)statement;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (authn != null && authn.getSessionIndex() != null) {
|
||||||
|
notes.put("SAML_FEDERATED_SESSION_INDEX", authn.getSessionIndex());
|
||||||
|
}
|
||||||
|
return callback.authenticated(notes, config, identity, relayState);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
|
||||||
|
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
|
||||||
|
|
||||||
|
if (assertions.isEmpty()) {
|
||||||
|
throw new IdentityBrokerException("No assertion from response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
|
||||||
|
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
|
||||||
|
|
||||||
|
if (encryptedAssertion != null) {
|
||||||
|
decryptAssertion(responseType, realm.getPrivateKey());
|
||||||
|
|
||||||
|
}
|
||||||
|
return responseType.getAssertions().get(0).getAssertion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response handleSamlResponse(String samlResponse, String relayState) {
|
||||||
|
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||||
|
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
|
||||||
|
// validate destination
|
||||||
|
if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
|
||||||
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
|
event.error(Errors.INVALID_SAML_RESPONSE);
|
||||||
|
event.detail(Details.REASON, "invalid_destination");
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
|
||||||
|
}
|
||||||
|
if (statusResponse instanceof ResponseType) {
|
||||||
|
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// todo need to check that it is actually a LogoutResponse
|
||||||
|
return handleLogoutResponse(holder, statusResponse, relayState);
|
||||||
|
}
|
||||||
|
//throw new RuntimeException("Unknown response type");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
|
||||||
|
if (config.isValidateSignature()) {
|
||||||
|
try {
|
||||||
|
verifySignature(holder);
|
||||||
|
} catch (VerificationException e) {
|
||||||
|
logger.error("logout response validation failed", e);
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
event.error(Errors.INVALID_SIGNATURE);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (relayState == null) {
|
||||||
|
logger.error("no valid user session");
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
|
||||||
|
}
|
||||||
|
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
|
||||||
|
if (userSession == null) {
|
||||||
|
logger.error("no valid user session");
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
|
||||||
|
}
|
||||||
|
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||||
|
logger.error("usersession in different state");
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
event.error(Errors.USER_SESSION_NOT_FOUND);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
|
||||||
|
}
|
||||||
|
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
|
||||||
|
SAML2Response saml2Response = new SAML2Response();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Document doc = saml2Response.convert(responseType);
|
||||||
|
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||||
|
|
||||||
|
if (enc == null) {
|
||||||
|
throw new IdentityBrokerException("No encrypted assertion found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
|
||||||
|
Document newDoc = DocumentUtil.createDocument();
|
||||||
|
Node importedNode = newDoc.importNode(enc, true);
|
||||||
|
newDoc.appendChild(importedNode);
|
||||||
|
|
||||||
|
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
|
||||||
|
SAMLParser parser = new SAMLParser();
|
||||||
|
|
||||||
|
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
|
||||||
|
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
|
||||||
|
.getNodeAsStream(decryptedDocumentElement)));
|
||||||
|
|
||||||
|
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
||||||
|
|
||||||
|
return responseType;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not decrypt assertion.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class PostBinding extends Binding {
|
||||||
|
@Override
|
||||||
|
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
|
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
|
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||||
|
return SAMLRequestParser.parseResponsePostBinding(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getBindingType() {
|
||||||
|
return SamlProtocol.SAML_POST_BINDING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class RedirectBinding extends Binding {
|
||||||
|
@Override
|
||||||
|
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
|
PublicKey publicKey = getIDPKey();
|
||||||
|
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
|
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SAMLDocumentHolder extractResponseDocument(String response) {
|
||||||
|
return SAMLRequestParser.parseRequestRedirectBinding(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getBindingType() {
|
||||||
|
return SamlProtocol.SAML_REDIRECT_BINDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,13 +23,19 @@ import org.keycloak.broker.provider.AuthenticationRequest;
|
|||||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||||
import org.keycloak.broker.provider.FederatedIdentity;
|
import org.keycloak.broker.provider.FederatedIdentity;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||||
|
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
|
||||||
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||||
|
import org.keycloak.services.managers.EventsManager;
|
||||||
import org.picketlink.common.constants.JBossSAMLConstants;
|
import org.picketlink.common.constants.JBossSAMLConstants;
|
||||||
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
import org.picketlink.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.picketlink.common.exceptions.ConfigurationException;
|
||||||
|
import org.picketlink.common.exceptions.ParsingException;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
import org.picketlink.common.util.DocumentUtil;
|
import org.picketlink.common.util.DocumentUtil;
|
||||||
import org.picketlink.common.util.StaxParserUtil;
|
import org.picketlink.common.util.StaxParserUtil;
|
||||||
@@ -63,6 +69,7 @@ import javax.ws.rs.core.Response;
|
|||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
@@ -84,6 +91,11 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object callback(RealmModel realm, Callback callback) {
|
||||||
|
return new SAMLEndpoint(realm, getConfig(), callback);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationResponse handleRequest(AuthenticationRequest request) {
|
public AuthenticationResponse handleRequest(AuthenticationRequest request) {
|
||||||
try {
|
try {
|
||||||
@@ -99,12 +111,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
|
|
||||||
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
|
||||||
|
|
||||||
|
String assertionConsumerServiceUrl = UriBuilder.fromUri(request.getRedirectUri()).path("endpoint").build().toString();
|
||||||
|
|
||||||
if (getConfig().isPostBindingResponse()) {
|
if (getConfig().isPostBindingResponse()) {
|
||||||
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
|
SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
|
||||||
.assertionConsumerUrl(request.getRedirectUri())
|
.assertionConsumerUrl(assertionConsumerServiceUrl)
|
||||||
.destination(destinationUrl)
|
.destination(destinationUrl)
|
||||||
.issuer(issuerURL)
|
.issuer(issuerURL)
|
||||||
.forceAuthn(getConfig().isForceAuthn())
|
.forceAuthn(getConfig().isForceAuthn())
|
||||||
@@ -292,6 +306,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
return requestParameters.getFirst(parameterName);
|
return requestParameters.getFirst(parameterName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response logout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
|
||||||
|
if (getConfig().getSingleLogoutServiceUrl() == null) return null;
|
||||||
|
|
||||||
|
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
|
||||||
|
.issuer(getEntityId(uriInfo, realm))
|
||||||
|
.sessionIndex(userSession.getNote("SAML_FEDERATED_SESSION_INDEX"))
|
||||||
|
.userPrincipal(userSession.getNote("SAML_FEDERATED_SUBJECT"), userSession.getNote("SAML_FEDERATED_SUBJECT_NAMEFORMAT"))
|
||||||
|
.destination(getConfig().getSingleLogoutServiceUrl());
|
||||||
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
|
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
|
.signDocument();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return logoutBuilder.relayState(userSession.getId()).postBinding().request();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
|
public Response export(UriInfo uriInfo, RealmModel realm, String format) {
|
||||||
|
|
||||||
@@ -301,7 +336,12 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
authnBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
String assertionConsumerService = uriInfo.getBaseUriBuilder().path("realms").path(realm.getName()).path("broker").path(getConfig().getAlias()).build().toString();
|
String endpoint = uriInfo.getBaseUriBuilder()
|
||||||
|
.path("realms").path(realm.getName())
|
||||||
|
.path("broker")
|
||||||
|
.path(getConfig().getAlias())
|
||||||
|
.path("endpoint")
|
||||||
|
.build().toString();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -312,9 +352,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
||||||
" </NameIDFormat>\n" +
|
" </NameIDFormat>\n" +
|
||||||
// todo single logout service description
|
// todo single logout service description
|
||||||
// " <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8081/sales-metadata/\"/>\n" +
|
" <SingleLogoutService Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"/>\n" +
|
||||||
" <AssertionConsumerService\n" +
|
" <AssertionConsumerService\n" +
|
||||||
" Binding=\"" + authnBinding + "\" Location=\"" + assertionConsumerService + "\"\n" +
|
" Binding=\"" + authnBinding + "\" Location=\"" + endpoint + "\"\n" +
|
||||||
" index=\"1\" isDefault=\"true\" />\n";
|
" index=\"1\" isDefault=\"true\" />\n";
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
descriptor +=
|
descriptor +=
|
||||||
|
|||||||
Regular → Executable
+8
@@ -39,6 +39,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
|||||||
getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
|
getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSingleLogoutServiceUrl() {
|
||||||
|
return getConfig().get("singleLogoutServiceUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleLogoutServiceUrl(String singleLogoutServiceUrl) {
|
||||||
|
getConfig().put("singleLogoutServiceUrl", singleLogoutServiceUrl);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isValidateSignature() {
|
public boolean isValidateSignature() {
|
||||||
return Boolean.valueOf(getConfig().get("validateSignature"));
|
return Boolean.valueOf(getConfig().get("validateSignature"));
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -56,7 +56,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> parseConfig(InputStream inputStream) {
|
public Map<String, String> parseConfig(InputStream inputStream) {
|
||||||
try {
|
try {
|
||||||
Object parsedObject = new SAMLParser().parse(inputStream);
|
Object parsedObject = new SAMLParser().parse(inputStream);
|
||||||
EntityDescriptorType entityType;
|
EntityDescriptorType entityType;
|
||||||
@@ -90,6 +90,18 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
String singleLogoutServiceUrl = null;
|
||||||
|
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
||||||
|
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
||||||
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
||||||
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public interface Errors {
|
|||||||
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
String INVALID_REDIRECT_URI = "invalid_redirect_uri";
|
||||||
String INVALID_CODE = "invalid_code";
|
String INVALID_CODE = "invalid_code";
|
||||||
String INVALID_TOKEN = "invalid_token";
|
String INVALID_TOKEN = "invalid_token";
|
||||||
|
String INVALID_SAML_RESPONSE = "invalid_saml_response";
|
||||||
String INVALID_SAML_AUTHN_REQUEST = "invalid_authn_request";
|
String INVALID_SAML_AUTHN_REQUEST = "invalid_authn_request";
|
||||||
String INVALID_SAML_LOGOUT_REQUEST = "invalid_logout_request";
|
String INVALID_SAML_LOGOUT_REQUEST = "invalid_logout_request";
|
||||||
String INVALID_SAML_LOGOUT_RESPONSE = "invalid_logout_response";
|
String INVALID_SAML_LOGOUT_RESPONSE = "invalid_logout_response";
|
||||||
|
|||||||
+7
@@ -44,6 +44,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span>
|
<span tooltip-placement="right" tooltip="The Url that must be used to send authentication requests(SAML AuthnRequest)." class="fa fa-info-circle"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
|
||||||
|
<label class="col-sm-2 control-label" for="singleSignOnServiceUrl">Single Logout Service Url</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input class="form-control" id="singleLogoutServiceUrl" type="text" ng-model="identityProvider.config.singleLogoutServiceUrl" required>
|
||||||
|
</div>
|
||||||
|
<span tooltip-placement="right" tooltip="The Url that must be used to send logout requests." class="fa fa-info-circle"></span>
|
||||||
|
</div>
|
||||||
<div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
|
<div class="form-group clearfix" data-ng-show="!importFile && !importUrl">
|
||||||
<label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
|
<label class="col-sm-2 control-label" for="nameIDPolicyFormat">NameID Policy Format</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|||||||
+8
-2
@@ -43,8 +43,14 @@ public class SALM2LoginResponseBuilder {
|
|||||||
protected String requestID;
|
protected String requestID;
|
||||||
protected String authMethod;
|
protected String authMethod;
|
||||||
protected String requestIssuer;
|
protected String requestIssuer;
|
||||||
|
protected String sessionIndex;
|
||||||
|
|
||||||
|
|
||||||
|
public SALM2LoginResponseBuilder sessionIndex(String sessionIndex) {
|
||||||
|
this.sessionIndex = sessionIndex;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SALM2LoginResponseBuilder destination(String destination) {
|
public SALM2LoginResponseBuilder destination(String destination) {
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
return this;
|
return this;
|
||||||
@@ -135,8 +141,8 @@ public class SALM2LoginResponseBuilder {
|
|||||||
|
|
||||||
AuthnStatementType authnStatement = StatementUtil.createAuthnStatement(XMLTimeUtil.getIssueInstant(),
|
AuthnStatementType authnStatement = StatementUtil.createAuthnStatement(XMLTimeUtil.getIssueInstant(),
|
||||||
authContextRef);
|
authContextRef);
|
||||||
|
if (sessionIndex != null) authnStatement.setSessionIndex(sessionIndex);
|
||||||
authnStatement.setSessionIndex(assertion.getID());
|
else authnStatement.setSessionIndex(assertion.getID());
|
||||||
|
|
||||||
assertion.addStatement(authnStatement);
|
assertion.addStatement(authnStatement);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -19,6 +19,7 @@ import java.net.URI;
|
|||||||
public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRequestBuilder> {
|
public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRequestBuilder> {
|
||||||
protected String userPrincipal;
|
protected String userPrincipal;
|
||||||
protected String userPrincipalFormat;
|
protected String userPrincipalFormat;
|
||||||
|
protected String sessionIndex;
|
||||||
|
|
||||||
public SAML2LogoutRequestBuilder userPrincipal(String nameID, String nameIDformat) {
|
public SAML2LogoutRequestBuilder userPrincipal(String nameID, String nameIDformat) {
|
||||||
this.userPrincipal = nameID;
|
this.userPrincipal = nameID;
|
||||||
@@ -26,6 +27,11 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SAML2LogoutRequestBuilder sessionIndex(String index) {
|
||||||
|
this.sessionIndex = index;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public RedirectBindingBuilder redirectBinding() throws ConfigurationException, ProcessingException, ParsingException {
|
public RedirectBindingBuilder redirectBinding() throws ConfigurationException, ProcessingException, ParsingException {
|
||||||
Document samlResponseDocument = buildDocument();
|
Document samlResponseDocument = buildDocument();
|
||||||
return new RedirectBindingBuilder(samlResponseDocument);
|
return new RedirectBindingBuilder(samlResponseDocument);
|
||||||
@@ -58,7 +64,7 @@ public class SAML2LogoutRequestBuilder extends SAML2BindingBuilder<SAML2LogoutRe
|
|||||||
issuerID.setValue(issuer);
|
issuerID.setValue(issuer);
|
||||||
lort.setIssuer(issuerID);
|
lort.setIssuer(issuerID);
|
||||||
}
|
}
|
||||||
|
if (sessionIndex != null) lort.addSessionIndex(sessionIndex);
|
||||||
|
|
||||||
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
long assertionValidity = PicketLinkCoreSTS.instance().getConfiguration().getIssuedTokenTimeout();
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
|||||||
import org.jboss.resteasy.client.ClientRequest;
|
import org.jboss.resteasy.client.ClientRequest;
|
||||||
import org.jboss.resteasy.client.ClientResponse;
|
import org.jboss.resteasy.client.ClientResponse;
|
||||||
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
|
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.ClaimMask;
|
import org.keycloak.models.ClaimMask;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
@@ -97,6 +98,8 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
|
|
||||||
protected HttpHeaders headers;
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
|
protected EventBuilder event;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlProtocol setSession(KeycloakSession session) {
|
public SamlProtocol setSession(KeycloakSession session) {
|
||||||
@@ -122,6 +125,13 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SamlProtocol setEventBuilder(EventBuilder event) {
|
||||||
|
this.event = event;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||||
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
|
||||||
@@ -265,6 +275,7 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
builder.requestID(requestID)
|
builder.requestID(requestID)
|
||||||
.destination(redirectUri)
|
.destination(redirectUri)
|
||||||
.issuer(responseIssuer)
|
.issuer(responseIssuer)
|
||||||
|
.sessionIndex(clientSession.getId())
|
||||||
.requestIssuer(clientSession.getClient().getClientId())
|
.requestIssuer(clientSession.getClient().getClientId())
|
||||||
.nameIdentifier(nameIdFormat, nameId)
|
.nameIdentifier(nameIdFormat, nameId)
|
||||||
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
|
||||||
@@ -353,10 +364,6 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
return "true".equals(client.getAttribute(SAML_AUTHNSTATEMENT));
|
return "true".equals(client.getAttribute(SAML_AUTHNSTATEMENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean multivaluedRoles(ClientModel client) {
|
|
||||||
return "true".equals(client.getAttribute(SAML_MULTIVALUED_ROLES));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SignatureAlgorithm getSignatureAlgorithm(ClientModel client) {
|
public static SignatureAlgorithm getSignatureAlgorithm(ClientModel client) {
|
||||||
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
|
String alg = client.getAttribute(SAML_SIGNATURE_ALGORITHM);
|
||||||
if (alg != null) {
|
if (alg != null) {
|
||||||
@@ -460,10 +467,13 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
@Override
|
@Override
|
||||||
public Response finishLogout(UserSessionModel userSession) {
|
public Response finishLogout(UserSessionModel userSession) {
|
||||||
logger.debug("finishLogout");
|
logger.debug("finishLogout");
|
||||||
|
String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI);
|
||||||
|
String logoutRelayState = userSession.getNote(SAML_LOGOUT_RELAY_STATE);
|
||||||
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
|
||||||
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
|
builder.logoutRequestID(userSession.getNote(SAML_LOGOUT_REQUEST_ID));
|
||||||
builder.destination(userSession.getNote(SAML_LOGOUT_ISSUER));
|
builder.destination(logoutBindingUri);
|
||||||
builder.issuer(getResponseIssuer(realm));
|
builder.issuer(getResponseIssuer(realm));
|
||||||
|
builder.relayState(logoutRelayState);
|
||||||
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
|
String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
|
||||||
if (signingAlgorithm != null) {
|
if (signingAlgorithm != null) {
|
||||||
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
|
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
|
||||||
@@ -474,9 +484,9 @@ public class SamlProtocol implements LoginProtocol {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (isLogoutPostBindingForInitiator(userSession)) {
|
if (isLogoutPostBindingForInitiator(userSession)) {
|
||||||
return builder.postBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
|
return builder.postBinding().response(logoutBindingUri);
|
||||||
} else {
|
} else {
|
||||||
return builder.redirectBinding().response(userSession.getNote(SAML_LOGOUT_BINDING_URI));
|
return builder.redirectBinding().response(logoutBindingUri);
|
||||||
}
|
}
|
||||||
} catch (ConfigurationException e) {
|
} catch (ConfigurationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ package org.keycloak.protocol.saml;
|
|||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.util.PemUtils;
|
import org.keycloak.util.PemUtils;
|
||||||
|
import org.picketlink.common.constants.GeneralConstants;
|
||||||
import org.picketlink.common.exceptions.ProcessingException;
|
import org.picketlink.common.exceptions.ProcessingException;
|
||||||
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
|
||||||
|
import org.picketlink.identity.federation.web.util.RedirectBindingUtil;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.Signature;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,8 +26,12 @@ public class SamlProtocolUtils {
|
|||||||
if (!"true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
|
if (!"true".equals(client.getAttribute(SamlProtocol.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SAML2Signature saml2Signature = new SAML2Signature();
|
|
||||||
PublicKey publicKey = getSignatureValidationKey(client);
|
PublicKey publicKey = getSignatureValidationKey(client);
|
||||||
|
verifyDocumentSignature(document, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
|
||||||
|
SAML2Signature saml2Signature = new SAML2Signature();
|
||||||
try {
|
try {
|
||||||
if (!saml2Signature.validate(document, publicKey)) {
|
if (!saml2Signature.validate(document, publicKey)) {
|
||||||
throw new VerificationException("Invalid signature on document");
|
throw new VerificationException("Invalid signature on document");
|
||||||
@@ -51,5 +61,43 @@ public class SamlProtocolUtils {
|
|||||||
return cert.getPublicKey();
|
return cert.getPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation) throws VerificationException {
|
||||||
|
MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
|
||||||
|
String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
|
||||||
|
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
||||||
|
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
|
||||||
|
String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
||||||
|
|
||||||
|
if (request == null) throw new VerificationException("SAMLRequest as null");
|
||||||
|
if (algorithm == null) throw new VerificationException("SigAlg as null");
|
||||||
|
if (signature == null) throw new VerificationException("Signature as null");
|
||||||
|
|
||||||
|
// Shibboleth doesn't sign the document for redirect binding.
|
||||||
|
// todo maybe a flag?
|
||||||
|
|
||||||
|
|
||||||
|
UriBuilder builder = UriBuilder.fromPath("/")
|
||||||
|
.queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
|
||||||
|
if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
|
||||||
|
builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
|
||||||
|
}
|
||||||
|
builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
|
||||||
|
String rawQuery = builder.build().getRawQuery();
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
|
||||||
|
|
||||||
|
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
|
||||||
|
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
|
||||||
|
validator.initVerify(publicKey);
|
||||||
|
validator.update(rawQuery.getBytes("UTF-8"));
|
||||||
|
if (!validator.verify(decodedSignature)) {
|
||||||
|
throw new VerificationException("Invalid query param signature");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new VerificationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,6 @@ public class SamlService {
|
|||||||
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_ISSUER, logoutRequest.getIssuer().getValue());
|
|
||||||
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
|
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
|
||||||
// remove client from logout requests
|
// remove client from logout requests
|
||||||
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
|
||||||
@@ -446,47 +445,12 @@ public class SamlService {
|
|||||||
if (!"true".equals(client.getAttribute("saml.client.signature"))) {
|
if (!"true".equals(client.getAttribute("saml.client.signature"))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MultivaluedMap<String, String> encodedParams = uriInfo.getQueryParameters(false);
|
|
||||||
String request = encodedParams.getFirst(GeneralConstants.SAML_REQUEST_KEY);
|
|
||||||
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
|
|
||||||
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
|
|
||||||
|
|
||||||
if (request == null) throw new VerificationException("SAMLRequest as null");
|
|
||||||
if (algorithm == null) throw new VerificationException("SigAlg as null");
|
|
||||||
if (signature == null) throw new VerificationException("Signature as null");
|
|
||||||
|
|
||||||
// Shibboleth doesn't sign the document for redirect binding.
|
|
||||||
// todo maybe a flag?
|
|
||||||
// SamlProtocolUtils.verifyDocumentSignature(client, documentHolder.getSamlDocument());
|
|
||||||
|
|
||||||
PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
|
PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
|
||||||
|
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
|
||||||
|
|
||||||
UriBuilder builder = UriBuilder.fromPath("/")
|
|
||||||
.queryParam(GeneralConstants.SAML_REQUEST_KEY, request);
|
|
||||||
if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
|
|
||||||
builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
|
|
||||||
}
|
|
||||||
builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
|
|
||||||
String rawQuery = builder.build().getRawQuery();
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
|
|
||||||
|
|
||||||
SignatureAlgorithm signatureAlgorithm = SamlProtocol.getSignatureAlgorithm(client);
|
|
||||||
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
|
|
||||||
validator.initVerify(publicKey);
|
|
||||||
validator.update(rawQuery.getBytes("UTF-8"));
|
|
||||||
if (!validator.verify(decodedSignature)) {
|
|
||||||
throw new VerificationException("Invalid query param signature");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new VerificationException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
|
||||||
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -16,6 +18,29 @@ public enum SignatureAlgorithm {
|
|||||||
private final String xmlSignatureDigestMethod;
|
private final String xmlSignatureDigestMethod;
|
||||||
private final String javaSignatureAlgorithm;
|
private final String javaSignatureAlgorithm;
|
||||||
|
|
||||||
|
private static final Map<String, SignatureAlgorithm> signatureMethodMap = new HashMap<>();
|
||||||
|
private static final Map<String, SignatureAlgorithm> signatureDigestMethodMap = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
signatureMethodMap.put(RSA_SHA1.getXmlSignatureMethod(), RSA_SHA1);
|
||||||
|
signatureMethodMap.put(RSA_SHA256.getXmlSignatureMethod(), RSA_SHA256);
|
||||||
|
signatureMethodMap.put(RSA_SHA512.getXmlSignatureMethod(), RSA_SHA512);
|
||||||
|
signatureMethodMap.put(DSA_SHA1.getXmlSignatureMethod(), DSA_SHA1);
|
||||||
|
|
||||||
|
signatureDigestMethodMap.put(RSA_SHA1.getXmlSignatureDigestMethod(), RSA_SHA1);
|
||||||
|
signatureDigestMethodMap.put(RSA_SHA256.getXmlSignatureDigestMethod(), RSA_SHA256);
|
||||||
|
signatureDigestMethodMap.put(RSA_SHA512.getXmlSignatureDigestMethod(), RSA_SHA512);
|
||||||
|
signatureDigestMethodMap.put(DSA_SHA1.getXmlSignatureDigestMethod(), DSA_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureAlgorithm getFromXmlMethod(String xml) {
|
||||||
|
return signatureMethodMap.get(xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SignatureAlgorithm getFromXmlDigest(String xml) {
|
||||||
|
return signatureDigestMethodMap.get(xml);
|
||||||
|
}
|
||||||
|
|
||||||
SignatureAlgorithm(String xmlSignatureMethod, String xmlSignatureDigestMethod, String javaSignatureAlgorithm) {
|
SignatureAlgorithm(String xmlSignatureMethod, String xmlSignatureDigestMethod, String javaSignatureAlgorithm) {
|
||||||
this.xmlSignatureMethod = xmlSignatureMethod;
|
this.xmlSignatureMethod = xmlSignatureMethod;
|
||||||
this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
|
this.xmlSignatureDigestMethod = xmlSignatureDigestMethod;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.keycloak.protocol;
|
package org.keycloak.protocol;
|
||||||
|
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
@@ -24,6 +25,8 @@ public interface LoginProtocol extends Provider {
|
|||||||
|
|
||||||
LoginProtocol setHttpHeaders(HttpHeaders headers);
|
LoginProtocol setHttpHeaders(HttpHeaders headers);
|
||||||
|
|
||||||
|
LoginProtocol setEventBuilder(EventBuilder event);
|
||||||
|
|
||||||
Response cancelLogin(ClientSessionModel clientSession);
|
Response cancelLogin(ClientSessionModel clientSession);
|
||||||
Response invalidSessionError(ClientSessionModel clientSession);
|
Response invalidSessionError(ClientSessionModel clientSession);
|
||||||
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ package org.keycloak.protocol.oidc;
|
|||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
|
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
@@ -54,6 +57,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||||||
public static final String CLIENT_ID_PARAM = "client_id";
|
public static final String CLIENT_ID_PARAM = "client_id";
|
||||||
public static final String PROMPT_PARAM = "prompt";
|
public static final String PROMPT_PARAM = "prompt";
|
||||||
public static final String LOGIN_HINT_PARAM = "login_hint";
|
public static final String LOGIN_HINT_PARAM = "login_hint";
|
||||||
|
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
|
private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
|
||||||
|
|
||||||
@@ -65,11 +69,14 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||||||
|
|
||||||
protected HttpHeaders headers;
|
protected HttpHeaders headers;
|
||||||
|
|
||||||
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
protected EventBuilder event;
|
||||||
|
|
||||||
|
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
this.uriInfo = uriInfo;
|
this.uriInfo = uriInfo;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OIDCLoginProtocol(){
|
public OIDCLoginProtocol(){
|
||||||
@@ -100,6 +107,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OIDCLoginProtocol setEventBuilder(EventBuilder event) {
|
||||||
|
this.event = event;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response cancelLogin(ClientSessionModel clientSession) {
|
public Response cancelLogin(ClientSessionModel clientSession) {
|
||||||
String redirect = clientSession.getRedirectUri();
|
String redirect = clientSession.getRedirectUri();
|
||||||
@@ -168,7 +181,19 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response finishLogout(UserSessionModel userSession) {
|
public Response finishLogout(UserSessionModel userSession) {
|
||||||
throw new RuntimeException("NOT IMPLEMENTED");
|
String redirectUri = userSession.getNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI);
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
if (redirectUri != null) {
|
||||||
|
event.detail(Details.REDIRECT_URI, redirectUri);
|
||||||
|
}
|
||||||
|
event.user(userSession.getUser()).session(userSession).success();
|
||||||
|
|
||||||
|
|
||||||
|
if (redirectUri != null) {
|
||||||
|
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||||
|
} else {
|
||||||
|
return Response.ok().build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Regular → Executable
+1
@@ -37,6 +37,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||||||
config.setAuthorizationEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
config.setAuthorizationEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "auth").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||||
config.setTokenEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
config.setTokenEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "token").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||||
config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
config.setUserinfoEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||||
|
config.setLogoutEndpoint(uriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||||
config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
config.setJwksUri(uriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||||
|
|
||||||
config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||||
|
|||||||
+1
-1
@@ -254,7 +254,7 @@ public class AuthorizationEndpoint {
|
|||||||
if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
|
if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
|
||||||
|
|
||||||
if (prompt != null && prompt.equals("none")) {
|
if (prompt != null && prompt.equals("none")) {
|
||||||
OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers);
|
OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
|
||||||
return oauth.cancelLogin(clientSession);
|
return oauth.cancelLogin(clientSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+14
-9
@@ -78,23 +78,28 @@ public class LogoutEndpoint {
|
|||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@NoCache
|
@NoCache
|
||||||
public Response logout(final @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
|
public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
|
||||||
event.event(EventType.LOGOUT);
|
|
||||||
if (redirectUri != null) {
|
if (redirectUri != null) {
|
||||||
event.detail(Details.REDIRECT_URI, redirectUri);
|
String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
|
||||||
|
if (validatedUri == null) {
|
||||||
|
event.event(EventType.LOGOUT);
|
||||||
|
event.detail(Details.REDIRECT_URI, redirectUri);
|
||||||
|
event.error(Errors.INVALID_REDIRECT_URI);
|
||||||
|
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
|
||||||
|
}
|
||||||
|
redirectUri = validatedUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
logout(authResult.getSession());
|
if (redirectUri != null) authResult.getSession().setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirectUri);
|
||||||
|
authResult.getSession().setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||||
|
return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirectUri != null) {
|
if (redirectUri != null) {
|
||||||
String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
|
return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
|
||||||
if (validatedRedirect == null) {
|
|
||||||
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
|
|
||||||
}
|
|
||||||
return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
|
|
||||||
} else {
|
} else {
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
}
|
}
|
||||||
|
|||||||
Regular → Executable
+11
@@ -23,6 +23,9 @@ public class OIDCConfigurationRepresentation {
|
|||||||
@JsonProperty("userinfo_endpoint")
|
@JsonProperty("userinfo_endpoint")
|
||||||
private String userinfoEndpoint;
|
private String userinfoEndpoint;
|
||||||
|
|
||||||
|
@JsonProperty("end_session_endpoint")
|
||||||
|
private String logoutEndpoint;
|
||||||
|
|
||||||
@JsonProperty("jwks_uri")
|
@JsonProperty("jwks_uri")
|
||||||
private String jwksUri;
|
private String jwksUri;
|
||||||
|
|
||||||
@@ -81,6 +84,14 @@ public class OIDCConfigurationRepresentation {
|
|||||||
this.jwksUri = jwksUri;
|
this.jwksUri = jwksUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogoutEndpoint() {
|
||||||
|
return logoutEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogoutEndpoint(String logoutEndpoint) {
|
||||||
|
this.logoutEndpoint = logoutEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getGrantTypesSupported() {
|
public List<String> getGrantTypesSupported() {
|
||||||
return grantTypesSupported;
|
return grantTypesSupported;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.jboss.resteasy.spi.HttpRequest;
|
|||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
@@ -26,6 +27,7 @@ import org.keycloak.protocol.LoginProtocol;
|
|||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
@@ -141,9 +143,6 @@ public class AuthenticationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirectClients.size() == 0) {
|
|
||||||
return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
|
|
||||||
}
|
|
||||||
for (ClientSessionModel nextRedirectClient : redirectClients) {
|
for (ClientSessionModel nextRedirectClient : redirectClients) {
|
||||||
String authMethod = nextRedirectClient.getAuthMethod();
|
String authMethod = nextRedirectClient.getAuthMethod();
|
||||||
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
|
LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
|
||||||
@@ -164,18 +163,26 @@ public class AuthenticationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
String brokerId = userSession.getNote(IdentityBrokerService.BROKER_PROVIDER_ID);
|
||||||
|
if (brokerId != null) {
|
||||||
|
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
|
||||||
|
Response response = identityProvider.logout(userSession, uriInfo, realm);
|
||||||
|
if (response != null) return response;
|
||||||
|
}
|
||||||
return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
|
return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
public static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
|
||||||
expireIdentityCookie(realm, uriInfo, connection);
|
expireIdentityCookie(realm, uriInfo, connection);
|
||||||
expireRememberMeCookie(realm, uriInfo, connection);
|
expireRememberMeCookie(realm, uriInfo, connection);
|
||||||
userSession.setState(UserSessionModel.State.LOGGED_OUT);
|
userSession.setState(UserSessionModel.State.LOGGED_OUT);
|
||||||
String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
|
String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
|
||||||
|
EventBuilder event = new EventsManager(realm, session, connection).createEventBuilder();
|
||||||
LoginProtocol protocol = session.getProvider(LoginProtocol.class, method);
|
LoginProtocol protocol = session.getProvider(LoginProtocol.class, method);
|
||||||
protocol.setRealm(realm)
|
protocol.setRealm(realm)
|
||||||
.setHttpHeaders(headers)
|
.setHttpHeaders(headers)
|
||||||
.setUriInfo(uriInfo);
|
.setUriInfo(uriInfo)
|
||||||
|
.setEventBuilder(event);
|
||||||
Response response = protocol.finishLogout(userSession);
|
Response response = protocol.finishLogout(userSession);
|
||||||
session.sessions().removeUserSession(realm, userSession);
|
session.sessions().removeUserSession(realm, userSession);
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.keycloak.services.resources;
|
|||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.AuthenticationResponse;
|
import org.keycloak.broker.provider.AuthenticationResponse;
|
||||||
@@ -76,9 +77,10 @@ import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE;
|
|||||||
* @author Pedro Igor
|
* @author Pedro Igor
|
||||||
*/
|
*/
|
||||||
@Path("/broker")
|
@Path("/broker")
|
||||||
public class IdentityBrokerService {
|
public class IdentityBrokerService implements IdentityProvider.Callback {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(IdentityBrokerService.class);
|
private static final Logger LOGGER = Logger.getLogger(IdentityBrokerService.class);
|
||||||
|
public static final String BROKER_PROVIDER_ID = "BROKER_PROVIDER_ID";
|
||||||
|
|
||||||
private final RealmModel realmModel;
|
private final RealmModel realmModel;
|
||||||
|
|
||||||
@@ -121,8 +123,8 @@ public class IdentityBrokerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClientSessionCode clientSessionCode = parseClientSessionCode(code, providerId);
|
ClientSessionCode clientSessionCode = parseClientSessionCode(code);
|
||||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
|
||||||
AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, clientSessionCode));
|
AuthenticationResponse authenticationResponse = identityProvider.handleRequest(createAuthenticationRequest(providerId, clientSessionCode));
|
||||||
|
|
||||||
Response response = authenticationResponse.getResponse();
|
Response response = authenticationResponse.getResponse();
|
||||||
@@ -155,6 +157,17 @@ public class IdentityBrokerService {
|
|||||||
return handleResponse(providerId);
|
return handleResponse(providerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Path("{provider_id}/endpoint")
|
||||||
|
public Object getEndpoint(@PathParam("provider_id") String providerId) {
|
||||||
|
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
|
||||||
|
Object callback = identityProvider.callback(realmModel, this);
|
||||||
|
ResteasyProviderFactory.getInstance().injectProperties(callback);
|
||||||
|
//resourceContext.initResource(brokerService);
|
||||||
|
return callback;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Path("{provider_id}/token")
|
@Path("{provider_id}/token")
|
||||||
@OPTIONS
|
@OPTIONS
|
||||||
public Response retrieveTokenPreflight() {
|
public Response retrieveTokenPreflight() {
|
||||||
@@ -195,7 +208,7 @@ public class IdentityBrokerService {
|
|||||||
.createOAuthGrant(null), clientModel);
|
.createOAuthGrant(null), clientModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
|
||||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
||||||
|
|
||||||
if (identityProviderConfig.isStoreToken()) {
|
if (identityProviderConfig.isStoreToken()) {
|
||||||
@@ -233,6 +246,79 @@ public class IdentityBrokerService {
|
|||||||
return getToken(providerId, true);
|
return getToken(providerId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Response authenticated(Map<String, String> userNotes, IdentityProviderModel identityProviderConfig, FederatedIdentity federatedIdentity, String code) {
|
||||||
|
ClientSessionCode clientCode = null;
|
||||||
|
try {
|
||||||
|
clientCode = parseClientSessionCode(code);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, identityProviderConfig.getProviderId());
|
||||||
|
|
||||||
|
}
|
||||||
|
String providerId = identityProviderConfig.getAlias();
|
||||||
|
if (!identityProviderConfig.isStoreToken()) {
|
||||||
|
if (isDebugEnabled()) {
|
||||||
|
LOGGER.debugf("Token will not be stored for identity provider [%s].", providerId);
|
||||||
|
}
|
||||||
|
federatedIdentity.setToken(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
federatedIdentity.setIdentityProviderId(providerId);
|
||||||
|
ClientSessionModel clientSession = clientCode.getClientSession();
|
||||||
|
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, federatedIdentity.getId(),
|
||||||
|
federatedIdentity.getUsername(), federatedIdentity.getToken());
|
||||||
|
|
||||||
|
this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
|
||||||
|
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
|
||||||
|
.detail(Details.IDENTITY_PROVIDER_IDENTITY, federatedIdentity.getUsername());
|
||||||
|
|
||||||
|
UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
|
||||||
|
|
||||||
|
// Check if federatedUser is already authenticated (this means linking social into existing federatedUser account)
|
||||||
|
if (clientSession.getUserSession() != null) {
|
||||||
|
UserSessionModel userSession = clientSession.getUserSession();
|
||||||
|
for (Map.Entry<String, String> entry : userNotes.entrySet()) {
|
||||||
|
userSession.setNote(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return performAccountLinking(clientSession, providerId, federatedIdentityModel, federatedUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (federatedUser == null) {
|
||||||
|
try {
|
||||||
|
federatedUser = createUser(federatedIdentity);
|
||||||
|
|
||||||
|
if (identityProviderConfig.isUpdateProfileFirstLogin()) {
|
||||||
|
if (isDebugEnabled()) {
|
||||||
|
LOGGER.debugf("Identity provider requires update profile action.", federatedUser);
|
||||||
|
}
|
||||||
|
federatedUser.addRequiredAction(UPDATE_PROFILE);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return redirectToLoginPage(e, clientCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFederatedIdentity(federatedIdentity, federatedUser);
|
||||||
|
|
||||||
|
UserSessionModel userSession = this.session.sessions()
|
||||||
|
.createUserSession(this.realmModel, federatedUser, federatedUser.getUsername(), this.clientConnection.getRemoteAddr(), "broker", false);
|
||||||
|
|
||||||
|
this.event.user(federatedUser);
|
||||||
|
this.event.session(userSession);
|
||||||
|
|
||||||
|
TokenManager.attachClientSession(userSession, clientSession);
|
||||||
|
for (Map.Entry<String, String> entry : userNotes.entrySet()) {
|
||||||
|
userSession.setNote(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
userSession.setNote(BROKER_PROVIDER_ID, providerId);
|
||||||
|
|
||||||
|
if (isDebugEnabled()) {
|
||||||
|
LOGGER.debugf("Performing local authentication for user [%s].", federatedUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthenticationManager.nextActionAfterAuthentication(this.session, userSession, clientSession, this.clientConnection, this.request,
|
||||||
|
this.uriInfo, event);
|
||||||
|
}
|
||||||
|
|
||||||
private Response handleResponse(String providerId) {
|
private Response handleResponse(String providerId) {
|
||||||
if (isDebugEnabled()) {
|
if (isDebugEnabled()) {
|
||||||
LOGGER.debugf("Handling authentication response from identity provider [%s].", providerId);
|
LOGGER.debugf("Handling authentication response from identity provider [%s].", providerId);
|
||||||
@@ -242,7 +328,7 @@ public class IdentityBrokerService {
|
|||||||
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
IdentityProvider identityProvider = getIdentityProvider(providerId);
|
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
|
||||||
String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null));
|
String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null));
|
||||||
|
|
||||||
if (relayState == null) {
|
if (relayState == null) {
|
||||||
@@ -253,7 +339,7 @@ public class IdentityBrokerService {
|
|||||||
LOGGER.debugf("Relay state is valid: [%s].", relayState);
|
LOGGER.debugf("Relay state is valid: [%s].", relayState);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientSessionCode clientSessionCode = parseClientSessionCode(relayState, providerId);
|
ClientSessionCode clientSessionCode = parseClientSessionCode(relayState);
|
||||||
AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, clientSessionCode));
|
AuthenticationResponse authenticationResponse = identityProvider.handleResponse(createAuthenticationRequest(providerId, clientSessionCode));
|
||||||
Response response = authenticationResponse.getResponse();
|
Response response = authenticationResponse.getResponse();
|
||||||
|
|
||||||
@@ -386,7 +472,7 @@ public class IdentityBrokerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientSessionCode parseClientSessionCode(String code, String providerId) {
|
private ClientSessionCode parseClientSessionCode(String code) {
|
||||||
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
|
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
|
||||||
|
|
||||||
if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
|
if (clientCode != null && clientCode.isValid(AUTHENTICATE)) {
|
||||||
@@ -465,11 +551,11 @@ public class IdentityBrokerService {
|
|||||||
return Flows.errors().error(message, Status.BAD_REQUEST);
|
return Flows.errors().error(message, Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityProvider getIdentityProvider(String alias) {
|
public static IdentityProvider getIdentityProvider(KeycloakSession session, RealmModel realm, String alias) {
|
||||||
IdentityProviderModel identityProviderModel = this.realmModel.getIdentityProviderByAlias(alias);
|
IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(alias);
|
||||||
|
|
||||||
if (identityProviderModel != null) {
|
if (identityProviderModel != null) {
|
||||||
IdentityProviderFactory providerFactory = getIdentityProviderFactory(identityProviderModel);
|
IdentityProviderFactory providerFactory = getIdentityProviderFactory(session, identityProviderModel);
|
||||||
|
|
||||||
if (providerFactory == null) {
|
if (providerFactory == null) {
|
||||||
throw new IdentityBrokerException("Could not find factory for identity provider [" + alias + "].");
|
throw new IdentityBrokerException("Could not find factory for identity provider [" + alias + "].");
|
||||||
@@ -481,12 +567,12 @@ public class IdentityBrokerService {
|
|||||||
throw new IdentityBrokerException("Identity Provider [" + alias + "] not found.");
|
throw new IdentityBrokerException("Identity Provider [" + alias + "] not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityProviderFactory getIdentityProviderFactory(IdentityProviderModel model) {
|
private static IdentityProviderFactory getIdentityProviderFactory(KeycloakSession session, IdentityProviderModel model) {
|
||||||
Map<String, IdentityProviderFactory> availableProviders = new HashMap<String, IdentityProviderFactory>();
|
Map<String, IdentityProviderFactory> availableProviders = new HashMap<String, IdentityProviderFactory>();
|
||||||
List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
|
List<ProviderFactory> allProviders = new ArrayList<ProviderFactory>();
|
||||||
|
|
||||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
|
allProviders.addAll(session.getKeycloakSessionFactory().getProviderFactories(IdentityProvider.class));
|
||||||
allProviders.addAll(this.session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
|
allProviders.addAll(session.getKeycloakSessionFactory().getProviderFactories(SocialIdentityProvider.class));
|
||||||
|
|
||||||
for (ProviderFactory providerFactory : allProviders) {
|
for (ProviderFactory providerFactory : allProviders) {
|
||||||
availableProviders.put(providerFactory.getId(), (IdentityProviderFactory) providerFactory);
|
availableProviders.put(providerFactory.getId(), (IdentityProviderFactory) providerFactory);
|
||||||
|
|||||||
@@ -189,7 +189,8 @@ public class RealmsResource {
|
|||||||
|
|
||||||
IdentityBrokerService brokerService = new IdentityBrokerService(realm);
|
IdentityBrokerService brokerService = new IdentityBrokerService(realm);
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(brokerService);
|
ResteasyProviderFactory.getInstance().injectProperties(brokerService);
|
||||||
|
//resourceContext.initResource(brokerService);
|
||||||
|
|
||||||
brokerService.init();
|
brokerService.init();
|
||||||
|
|
||||||
return brokerService;
|
return brokerService;
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public class AccountTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @Ignore
|
//@Test @Ignore
|
||||||
public void runit() throws Exception {
|
public void runit() throws Exception {
|
||||||
Thread.sleep(10000000);
|
Thread.sleep(10000000);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -546,7 +546,7 @@ public abstract class AbstractIdentityProviderTest {
|
|||||||
this.loginPage.clickSocial(getProviderId());
|
this.loginPage.clickSocial(getProviderId());
|
||||||
|
|
||||||
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
|
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
|
||||||
|
System.out.println(this.driver.getCurrentUrl());
|
||||||
// log in to identity provider
|
// log in to identity provider
|
||||||
this.loginPage.login(username, "password");
|
this.loginPage.login(username, "password");
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -1,6 +1,7 @@
|
|||||||
package org.keycloak.testsuite.broker;
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
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;
|
||||||
@@ -60,7 +61,8 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
|
|||||||
try {
|
try {
|
||||||
SAML2Request saml2Request = new SAML2Request();
|
SAML2Request saml2Request = new SAML2Request();
|
||||||
ResponseType responseType = (ResponseType) saml2Request
|
ResponseType responseType = (ResponseType) saml2Request
|
||||||
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
|
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(pageSource));
|
||||||
|
//.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
|
||||||
|
|
||||||
assertNotNull(responseType);
|
assertNotNull(responseType);
|
||||||
assertFalse(responseType.getAssertions().isEmpty());
|
assertFalse(responseType.getAssertions().isEmpty());
|
||||||
@@ -68,4 +70,16 @@ public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
|
|||||||
fail("Could not parse token.");
|
fail("Could not parse token.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testSuccessfulAuthenticationWithoutUpdateProfile() {
|
||||||
|
super.testSuccessfulAuthenticationWithoutUpdateProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
public void testTokenStorageAndRetrievalByOAuthClient() {
|
||||||
|
super.testTokenStorageAndRetrievalByOAuthClient();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -60,7 +60,7 @@ public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityP
|
|||||||
try {
|
try {
|
||||||
SAML2Request saml2Request = new SAML2Request();
|
SAML2Request saml2Request = new SAML2Request();
|
||||||
ResponseType responseType = (ResponseType) saml2Request
|
ResponseType responseType = (ResponseType) saml2Request
|
||||||
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(URLDecoder.decode(pageSource, "UTF-8")));
|
.getSAML2ObjectFromStream(PostBindingUtil.base64DecodeAsStream(pageSource));
|
||||||
|
|
||||||
assertNotNull(responseType);
|
assertNotNull(responseType);
|
||||||
assertFalse(responseType.getAssertions().isEmpty());
|
assertFalse(responseType.getAssertions().isEmpty());
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"name": "http://localhost:8081/auth/realms/realm-with-broker",
|
"name": "http://localhost:8081/auth/realms/realm-with-broker",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-signed-idp"
|
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-signed-idp/endpoint"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.assertion.signature": "true",
|
"saml.assertion.signature": "true",
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
"name": "http://localhost:8081/auth/realms/realm-with-broker",
|
"name": "http://localhost:8081/auth/realms/realm-with-broker",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic"
|
"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.authnstatement": "true"
|
"saml.authnstatement": "true"
|
||||||
|
|||||||
@@ -45,10 +45,10 @@
|
|||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.authnstatement": "true",
|
"saml.authnstatement": "true",
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post"
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -61,10 +61,10 @@
|
|||||||
"http://localhost:8081/sales-post-sig/*"
|
"http://localhost:8081/sales-post-sig/*"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
"saml.signature.algorithm": "RSA_SHA256",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -84,10 +84,10 @@
|
|||||||
"http://localhost:8081/sales-post-sig-transient/*"
|
"http://localhost:8081/sales-post-sig-transient/*"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-transient",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-transient/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-transient",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-transient",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-transient/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-transient",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-transient/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
"saml.signature.algorithm": "RSA_SHA256",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -106,10 +106,10 @@
|
|||||||
"http://localhost:8081/sales-post-sig-persistent/*"
|
"http://localhost:8081/sales-post-sig-persistent/*"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-persistent",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-persistent/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-persistent",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-persistent",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-persistent/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-persistent",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-persistent/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
"saml.signature.algorithm": "RSA_SHA256",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -131,10 +131,10 @@
|
|||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_force_name_id_format": "true",
|
"saml_force_name_id_format": "true",
|
||||||
"saml_name_id_format": "email",
|
"saml_name_id_format": "email",
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-sig-email/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-sig-email/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-sig-email/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-email",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-sig-email/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA256",
|
"saml.signature.algorithm": "RSA_SHA256",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -148,8 +148,8 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8081/bad-realm-sales-post-sig",
|
"baseUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
|
||||||
"adminUrl": "http://localhost:8081/bad-realm-sales-post-sig",
|
"adminUrl": "http://localhost:8081/bad-realm-sales-post-sig/",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/bad-realm-sales-post-sig/*"
|
"http://localhost:8081/bad-realm-sales-post-sig/*"
|
||||||
],
|
],
|
||||||
@@ -166,8 +166,8 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"baseUrl": "http://localhost:8081/bad-client-sales-post-sig",
|
"baseUrl": "http://localhost:8081/bad-client-sales-post-sig/",
|
||||||
"adminUrl": "http://localhost:8081/bad-client-sales-post-sig",
|
"adminUrl": "http://localhost:8081/bad-client-sales-post-sig/",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/bad-client-sales-post-sig/*"
|
"http://localhost:8081/bad-client-sales-post-sig/*"
|
||||||
],
|
],
|
||||||
@@ -189,10 +189,10 @@
|
|||||||
"http://localhost:8081/sales-post-enc/*"
|
"http://localhost:8081/sales-post-enc/*"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-enc",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/sales-post-enc/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-enc",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/sales-post-enc/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-enc",
|
"saml_single_logout_service_url_post": "http://localhost:8081/sales-post-enc/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-enc",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/sales-post-enc/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA512",
|
"saml.signature.algorithm": "RSA_SHA512",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/employee-sig/*"
|
"http://localhost:8081/employee-sig/*"
|
||||||
],
|
],
|
||||||
"adminUrl": "http://localhost:8081/employee-sig",
|
"adminUrl": "http://localhost:8081/employee-sig/",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
@@ -279,15 +279,15 @@
|
|||||||
"protocol": "saml",
|
"protocol": "saml",
|
||||||
"fullScopeAllowed": true,
|
"fullScopeAllowed": true,
|
||||||
"frontchannelLogout": true,
|
"frontchannelLogout": true,
|
||||||
"baseUrl": "http://localhost:8081/employee-sig-front",
|
"baseUrl": "http://localhost:8081/employee-sig-front/",
|
||||||
"redirectUris": [
|
"redirectUris": [
|
||||||
"http://localhost:8081/employee-sig-front/*"
|
"http://localhost:8081/employee-sig-front/*"
|
||||||
],
|
],
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig-front",
|
"saml_assertion_consumer_url_post": "http://localhost:8081/employee-sig-front/",
|
||||||
"saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig-front",
|
"saml_assertion_consumer_url_redirect": "http://localhost:8081/employee-sig-front/",
|
||||||
"saml_single_logout_service_url_post": "http://localhost:8081/employee-sig-front",
|
"saml_single_logout_service_url_post": "http://localhost:8081/employee-sig-front/",
|
||||||
"saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig-front",
|
"saml_single_logout_service_url_redirect": "http://localhost:8081/employee-sig-front/",
|
||||||
"saml.server.signature": "true",
|
"saml.server.signature": "true",
|
||||||
"saml.client.signature": "true",
|
"saml.client.signature": "true",
|
||||||
"saml.signature.algorithm": "RSA_SHA1",
|
"saml.signature.algorithm": "RSA_SHA1",
|
||||||
|
|||||||
Reference in new issue
Block a user