mirror of https://github.com/apache/cloudstack.git
SAML: WIP redirections work now
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
18ff47efc0
commit
b82207e081
|
|
@ -26,11 +26,54 @@ import org.apache.cloudstack.api.Parameter;
|
|||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.LoginCmdResponse;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.opensaml.Configuration;
|
||||
import org.opensaml.DefaultBootstrap;
|
||||
import org.opensaml.common.SAMLVersion;
|
||||
import org.opensaml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml2.core.AuthnContextClassRef;
|
||||
import org.opensaml.saml2.core.AuthnContextComparisonTypeEnumeration;
|
||||
import org.opensaml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml2.core.Issuer;
|
||||
import org.opensaml.saml2.core.NameIDPolicy;
|
||||
import org.opensaml.saml2.core.RequestedAuthnContext;
|
||||
import org.opensaml.saml2.core.impl.AuthnContextClassRefBuilder;
|
||||
import org.opensaml.saml2.core.impl.AuthnRequestBuilder;
|
||||
import org.opensaml.saml2.core.impl.IssuerBuilder;
|
||||
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
|
||||
import org.opensaml.saml2.core.impl.RequestedAuthnContextBuilder;
|
||||
import org.opensaml.xml.ConfigurationException;
|
||||
import org.opensaml.xml.XMLObject;
|
||||
import org.opensaml.xml.io.Marshaller;
|
||||
import org.opensaml.xml.io.MarshallingException;
|
||||
import org.opensaml.xml.io.Unmarshaller;
|
||||
import org.opensaml.xml.io.UnmarshallerFactory;
|
||||
import org.opensaml.xml.io.UnmarshallingException;
|
||||
import org.opensaml.xml.util.Base64;
|
||||
import org.opensaml.xml.util.XMLHelper;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Map;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
@APICommand(name = "samlsso", description = "SP initiated SAML Single Sign On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {})
|
||||
public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
|
||||
|
|
@ -71,12 +114,206 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
|
|||
throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
|
||||
}
|
||||
|
||||
public String buildAuthnRequestUrl(String resourceUrl) {
|
||||
String randomId = new BigInteger(130, new SecureRandom()).toString(32);
|
||||
// TODO: Add method to get this url from metadata
|
||||
String identityProviderUrl = "https://idp.ssocircle.com:443/sso/SSORedirect/metaAlias/ssocircle";
|
||||
String encodedAuthRequest = "";
|
||||
|
||||
try {
|
||||
DefaultBootstrap.bootstrap();
|
||||
AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, identityProviderUrl, resourceUrl); // SAML AuthRequest
|
||||
encodedAuthRequest = encodeAuthnRequest(authnRequest);
|
||||
} catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException e) {
|
||||
s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
|
||||
}
|
||||
return identityProviderUrl + "?SAMLRequest=" + encodedAuthRequest; // + "&RelayState=" + relayState;
|
||||
}
|
||||
|
||||
private AuthnRequest buildAuthnRequestObject(String authnId, String idpUrl, String consumerUrl) {
|
||||
// Issuer object
|
||||
IssuerBuilder issuerBuilder = new IssuerBuilder();
|
||||
Issuer issuer = issuerBuilder.buildObject();
|
||||
//SAMLConstants.SAML20_NS,
|
||||
// "Issuer", "samlp");
|
||||
issuer.setValue("apache-cloudstack");
|
||||
|
||||
// NameIDPolicy
|
||||
NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
|
||||
NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
|
||||
nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
|
||||
nameIdPolicy.setSPNameQualifier("Apache CloudStack");
|
||||
nameIdPolicy.setAllowCreate(true);
|
||||
|
||||
// AuthnContextClass
|
||||
AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
|
||||
AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject(
|
||||
SAMLConstants.SAML20_NS,
|
||||
"AuthnContextClassRef", "saml");
|
||||
authnContextClassRef.setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
|
||||
|
||||
// AuthnContex
|
||||
RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
|
||||
RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
|
||||
requestedAuthnContext
|
||||
.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
|
||||
requestedAuthnContext.getAuthnContextClassRefs().add(
|
||||
authnContextClassRef);
|
||||
|
||||
|
||||
// Creation of AuthRequestObject
|
||||
AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
|
||||
AuthnRequest authnRequest = authRequestBuilder.buildObject();
|
||||
//SAMLConstants.SAML20P_NS,
|
||||
// "AuthnRequest", "samlp");
|
||||
authnRequest.setID(authnId);
|
||||
authnRequest.setDestination(idpUrl);
|
||||
authnRequest.setVersion(SAMLVersion.VERSION_20);
|
||||
authnRequest.setForceAuthn(true);
|
||||
authnRequest.setIsPassive(false);
|
||||
authnRequest.setIssuer(issuer);
|
||||
authnRequest.setIssueInstant(new DateTime());
|
||||
authnRequest.setProviderName("Apache CloudStack");
|
||||
authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
|
||||
authnRequest.setAssertionConsumerServiceURL(consumerUrl);
|
||||
//authnRequest.setNameIDPolicy(nameIdPolicy);
|
||||
//authnRequest.setRequestedAuthnContext(requestedAuthnContext);
|
||||
|
||||
return authnRequest;
|
||||
}
|
||||
|
||||
private String encodeAuthnRequest(AuthnRequest authnRequest)
|
||||
throws MarshallingException, IOException {
|
||||
|
||||
Marshaller marshaller = null;
|
||||
org.w3c.dom.Element authDOM = null;
|
||||
StringWriter requestWriter = null;
|
||||
String requestMessage = null;
|
||||
Deflater deflater = null;
|
||||
ByteArrayOutputStream byteArrayOutputStream = null;
|
||||
DeflaterOutputStream deflaterOutputStream = null;
|
||||
String encodedRequestMessage = null;
|
||||
|
||||
marshaller = org.opensaml.Configuration.getMarshallerFactory()
|
||||
.getMarshaller(authnRequest); // object to DOM converter
|
||||
|
||||
authDOM = marshaller.marshall(authnRequest); // converting to a DOM
|
||||
|
||||
requestWriter = new StringWriter();
|
||||
XMLHelper.writeNode(authDOM, requestWriter);
|
||||
requestMessage = requestWriter.toString(); // DOM to string
|
||||
|
||||
deflater = new Deflater(Deflater.DEFLATED, true);
|
||||
byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream,
|
||||
deflater);
|
||||
deflaterOutputStream.write(requestMessage.getBytes()); // compressing
|
||||
deflaterOutputStream.close();
|
||||
|
||||
encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream
|
||||
.toByteArray(), Base64.DONT_BREAK_LINES);
|
||||
encodedRequestMessage = URLEncoder.encode(encodedRequestMessage,
|
||||
"UTF-8").trim(); // encoding string
|
||||
|
||||
return encodedRequestMessage;
|
||||
}
|
||||
|
||||
|
||||
public String processResponseMessage(String responseMessage) {
|
||||
|
||||
XMLObject responseObject = null;
|
||||
|
||||
try {
|
||||
|
||||
responseObject = this.unmarshall(responseMessage);
|
||||
|
||||
} catch (ConfigurationException | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return this.getResult(responseObject);
|
||||
}
|
||||
|
||||
private XMLObject unmarshall(String responseMessage)
|
||||
throws ConfigurationException, ParserConfigurationException,
|
||||
SAXException, IOException, UnmarshallingException {
|
||||
|
||||
DocumentBuilderFactory documentBuilderFactory = null;
|
||||
DocumentBuilder docBuilder = null;
|
||||
Document document = null;
|
||||
Element element = null;
|
||||
UnmarshallerFactory unmarshallerFactory = null;
|
||||
Unmarshaller unmarshaller = null;
|
||||
|
||||
DefaultBootstrap.bootstrap();
|
||||
|
||||
documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
|
||||
docBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
|
||||
document = docBuilder.parse(new ByteArrayInputStream(responseMessage
|
||||
.trim().getBytes())); // response to DOM
|
||||
|
||||
element = document.getDocumentElement(); // the DOM element
|
||||
|
||||
unmarshallerFactory = Configuration.getUnmarshallerFactory();
|
||||
|
||||
unmarshaller = unmarshallerFactory.getUnmarshaller(element);
|
||||
|
||||
return unmarshaller.unmarshall(element); // Response object
|
||||
|
||||
}
|
||||
|
||||
private String getResult(XMLObject responseObject) {
|
||||
|
||||
Element ele = null;
|
||||
NodeList statusNodeList = null;
|
||||
Node statusNode = null;
|
||||
NamedNodeMap statusAttr = null;
|
||||
Node valueAtt = null;
|
||||
String statusValue = null;
|
||||
|
||||
String[] word = null;
|
||||
String result = null;
|
||||
|
||||
NodeList nameIDNodeList = null;
|
||||
Node nameIDNode = null;
|
||||
String nameID = null;
|
||||
|
||||
// reading the Response Object
|
||||
ele = responseObject.getDOM();
|
||||
statusNodeList = ele.getElementsByTagName("samlp:StatusCode");
|
||||
statusNode = statusNodeList.item(0);
|
||||
statusAttr = statusNode.getAttributes();
|
||||
valueAtt = statusAttr.item(0);
|
||||
statusValue = valueAtt.getNodeValue();
|
||||
|
||||
word = statusValue.split(":");
|
||||
result = word[word.length - 1];
|
||||
|
||||
nameIDNodeList = ele.getElementsByTagNameNS(
|
||||
"urn:oasis:names:tc:SAML:2.0:assertion", "NameID");
|
||||
nameIDNode = nameIDNodeList.item(0);
|
||||
nameID = nameIDNode.getFirstChild().getNodeValue();
|
||||
|
||||
result = nameID + ":" + result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
|
||||
|
||||
String response = null;
|
||||
try {
|
||||
resp.sendRedirect(getIdpUrl());
|
||||
String redirectUrl = buildAuthnRequestUrl("http://localhost:8080/client/api?command=login");
|
||||
resp.sendRedirect(redirectUrl);
|
||||
|
||||
//resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
|
||||
//resp.setHeader("Location", redirectUrl);
|
||||
|
||||
// TODO: create and send assertion with the URL as GET params
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue