You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bh...@apache.org on 2014/08/24 16:05:03 UTC
[43/50] [abbrv] git commit: updated refs/heads/saml2 to ba68616
saml: move refactor files from server to api module
- Move interfaces and classes from server to api module
- This can be then used for pluggable api authenticators
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/f1d85834
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/f1d85834
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/f1d85834
Branch: refs/heads/saml2
Commit: f1d858340f7faf7c56c6c68c7ef543599fd248a0
Parents: ffa09b5
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Sun Aug 24 15:51:29 2014 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Sun Aug 24 15:51:29 2014 +0200
----------------------------------------------------------------------
.../apache/cloudstack/api/ApiServerService.java | 45 +++
.../api/auth/APIAuthenticationManager.java | 24 ++
.../api/auth/APIAuthenticationType.java | 21 ++
.../cloudstack/api/auth/APIAuthenticator.java | 41 +++
.../cloudstack/SAML2UserAuthenticator.java | 65 ----
.../command/SAML2LoginAPIAuthenticatorCmd.java | 352 +++++++++++++++++++
.../command/SAML2LogoutAPIAuthenticatorCmd.java | 73 ++++
.../cloudstack/saml/SAML2UserAuthenticator.java | 65 ++++
server/src/com/cloud/api/ApiServerService.java | 45 ---
.../api/auth/APIAuthenticationManager.java | 24 --
.../cloud/api/auth/APIAuthenticationType.java | 21 --
.../com/cloud/api/auth/APIAuthenticator.java | 41 ---
.../api/auth/SAML2LoginAPIAuthenticatorCmd.java | 350 ------------------
.../auth/SAML2LogoutAPIAuthenticatorCmd.java | 71 ----
14 files changed, 621 insertions(+), 617 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/api/src/org/apache/cloudstack/api/ApiServerService.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiServerService.java b/api/src/org/apache/cloudstack/api/ApiServerService.java
new file mode 100644
index 0000000..9c0cfa3
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/ApiServerService.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api;
+
+import com.cloud.exception.CloudAuthenticationException;
+import org.apache.cloudstack.api.ResponseObject;
+import org.apache.cloudstack.api.ServerApiException;
+
+import javax.servlet.http.HttpSession;
+import java.util.Map;
+
+public interface ApiServerService {
+ public boolean verifyRequest(Map<String, Object[]> requestParameters, Long userId) throws ServerApiException;
+
+ public Long fetchDomainId(String domainUUID);
+
+ public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, String loginIpAddress,
+ Map<String, Object[]> requestParameters) throws CloudAuthenticationException;
+
+ public void logoutUser(long userId);
+
+ public boolean verifyUser(Long userId);
+
+ public String getSerializedApiError(int errorCode, String errorText, Map<String, Object[]> apiCommandParams, String responseType);
+
+ public String getSerializedApiError(ServerApiException ex, Map<String, Object[]> apiCommandParams, String responseType);
+
+ public String handleRequest(Map params, String responseType, StringBuilder auditTrailSb) throws ServerApiException;
+
+ public Class<?> getCmdClass(String cmdName);
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/api/src/org/apache/cloudstack/api/auth/APIAuthenticationManager.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/auth/APIAuthenticationManager.java b/api/src/org/apache/cloudstack/api/auth/APIAuthenticationManager.java
new file mode 100644
index 0000000..5d4d664
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/auth/APIAuthenticationManager.java
@@ -0,0 +1,24 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.auth;
+
+import com.cloud.utils.component.PluggableService;
+
+public interface APIAuthenticationManager extends PluggableService {
+ public APIAuthenticator getAPIAuthenticator(String name);
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/api/src/org/apache/cloudstack/api/auth/APIAuthenticationType.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/auth/APIAuthenticationType.java b/api/src/org/apache/cloudstack/api/auth/APIAuthenticationType.java
new file mode 100644
index 0000000..e8c7f0f
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/auth/APIAuthenticationType.java
@@ -0,0 +1,21 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.auth;
+
+public enum APIAuthenticationType {
+ LOGIN_API, LOGOUT_API
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/api/src/org/apache/cloudstack/api/auth/APIAuthenticator.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/auth/APIAuthenticator.java b/api/src/org/apache/cloudstack/api/auth/APIAuthenticator.java
new file mode 100644
index 0000000..20fe61f
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/auth/APIAuthenticator.java
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.auth;
+
+import org.apache.cloudstack.api.ServerApiException;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.Map;
+
+/*
+* APIAuthenticator is an interface that defines method that
+* a class should implement that help with Authentication and accepts
+* a command string and an array of parameters. This should be used only
+* in the Servlet that is doing authentication.
+*
+* @param command The API command name such as login, logout etc
+* @param params An array of HTTP parameters
+* @param session HttpSession object
+* */
+public interface APIAuthenticator {
+ public String authenticate(String command, Map<String, Object[]> params,
+ HttpSession session, String remoteAddress, String responseType,
+ StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException;
+ public APIAuthenticationType getAPIType();
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/plugins/user-authenticators/saml2/src/org/apache/cloudstack/SAML2UserAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/SAML2UserAuthenticator.java
deleted file mode 100644
index 4d4f1d3..0000000
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/SAML2UserAuthenticator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package org.apache.cloudstack;
-
-import com.cloud.server.auth.DefaultUserAuthenticator;
-import com.cloud.server.auth.UserAuthenticator;
-import com.cloud.user.User;
-import com.cloud.user.UserAccount;
-import com.cloud.user.dao.UserAccountDao;
-import com.cloud.user.dao.UserDao;
-import com.cloud.utils.Pair;
-import org.apache.log4j.Logger;
-
-import javax.ejb.Local;
-import javax.inject.Inject;
-import java.util.Map;
-
-@Local(value = {UserAuthenticator.class})
-public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
- public static final Logger s_logger = Logger.getLogger(SAML2UserAuthenticator.class);
-
- @Inject
- private UserAccountDao _userAccountDao;
- @Inject
- private UserDao _userDao;
-
- @Override
- public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
- if (s_logger.isDebugEnabled()) {
- s_logger.debug("Trying SAML2 auth for user: " + username);
- }
- final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
- if (userAccount == null) {
- s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
- return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
- } else {
- User user = _userDao.getUser(userAccount.getId());
- // TODO: check SAMLRequest, signature etc. from requestParameters
- if (user != null && user.getUuid().startsWith("saml")) {
- return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
- }
- }
- // Deny all by default
- return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
- }
-
- @Override
- public String encode(final String password) {
- // TODO: Complete method
- StringBuilder sb = new StringBuilder(32);
- return sb.toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
new file mode 100644
index 0000000..611c69b
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java
@@ -0,0 +1,352 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.api.command;
+
+import org.apache.cloudstack.api.ApiServerService;
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.exception.CloudAuthenticationException;
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import com.cloud.utils.HttpUtils;
+import com.cloud.utils.db.EntityManager;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.response.LoginCmdResponse;
+import org.apache.cloudstack.context.CallContext;
+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.Assertion;
+import org.opensaml.saml2.core.Attribute;
+import org.opensaml.saml2.core.AttributeStatement;
+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.NameID;
+import org.opensaml.saml2.core.NameIDPolicy;
+import org.opensaml.saml2.core.NameIDType;
+import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.opensaml.saml2.core.Response;
+import org.opensaml.saml2.core.StatusCode;
+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.signature.Signature;
+import org.opensaml.xml.util.Base64;
+import org.opensaml.xml.util.XMLHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import javax.inject.Inject;
+import javax.servlet.http.Cookie;
+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.List;
+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 {
+ public static final Logger s_logger = Logger.getLogger(SAML2LoginAPIAuthenticatorCmd.class.getName());
+ private static final String s_name = "loginresponse";
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+ @Parameter(name = ApiConstants.IDP_URL, type = CommandType.STRING, description = "Identity Provider SSO HTTP-Redirect binding URL", required = true)
+ private String idpUrl;
+
+ @Inject
+ ApiServerService _apiServer;
+ @Inject
+ EntityManager _entityMgr;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public String getIdpUrl() {
+ return idpUrl;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_TYPE_NORMAL;
+ }
+
+ @Override
+ public void execute() throws ServerApiException {
+ // We should never reach here
+ throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+ }
+
+ public String buildAuthnRequestUrl(String consumerUrl, String identityProviderUrl) {
+ String randomId = new BigInteger(130, new SecureRandom()).toString(32);
+ String spId = "org.apache.cloudstack";
+ String redirectUrl = "";
+ try {
+ DefaultBootstrap.bootstrap();
+ AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, spId, identityProviderUrl, consumerUrl);
+ redirectUrl = identityProviderUrl + "?SAMLRequest=" + encodeAuthnRequest(authnRequest);
+ } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException e) {
+ s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
+ }
+ return redirectUrl;
+ }
+
+ private AuthnRequest buildAuthnRequestObject(String authnId, String spId, String idpUrl, String consumerUrl) {
+ // Issuer object
+ IssuerBuilder issuerBuilder = new IssuerBuilder();
+ Issuer issuer = issuerBuilder.buildObject();
+ issuer.setValue(spId);
+
+ // NameIDPolicy
+ NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
+ NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
+ nameIdPolicy.setFormat(NameIDType.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.MINIMUM);
+ requestedAuthnContext.getAuthnContextClassRefs().add(
+ authnContextClassRef);
+
+ // Creation of AuthRequestObject
+ AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
+ AuthnRequest authnRequest = authRequestBuilder.buildObject();
+ 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 = Configuration.getMarshallerFactory()
+ .getMarshaller(authnRequest);
+ Element authDOM = marshaller.marshall(authnRequest);
+ StringWriter requestWriter = new StringWriter();
+ XMLHelper.writeNode(authDOM, requestWriter);
+ String requestMessage = requestWriter.toString();
+ Deflater deflater = new Deflater(Deflater.DEFLATED, true);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
+ deflaterOutputStream.write(requestMessage.getBytes());
+ deflaterOutputStream.close();
+ String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
+ encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
+ return encodedRequestMessage;
+ }
+
+ public Response processSAMLResponse(String responseMessage) {
+ XMLObject responseObject = null;
+ try {
+ responseObject = this.unmarshall(responseMessage);
+
+ } catch (ConfigurationException | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) {
+ s_logger.error("SAMLResponse processing error: " + e.getMessage());
+ }
+ return (Response) responseObject;
+ }
+
+ private XMLObject unmarshall(String responseMessage)
+ throws ConfigurationException, ParserConfigurationException,
+ SAXException, IOException, UnmarshallingException {
+ try {
+ DefaultBootstrap.bootstrap();
+ } catch (ConfigurationException | FactoryConfigurationError e) {
+ s_logger.error("SAML response message decoding error: " + e.getMessage());
+ }
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
+ byte[] base64DecodedResponse = Base64.decode(responseMessage);
+ Document document = docBuilder.parse(new ByteArrayInputStream(base64DecodedResponse));
+ Element element = document.getDocumentElement();
+ UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
+ Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
+ return unmarshaller.unmarshall(element);
+ }
+
+ @Override
+ public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
+ try {
+ if (!params.containsKey("SAMLResponse")) {
+ final String[] idps = (String[])params.get("idpurl");
+ String redirectUrl = buildAuthnRequestUrl("http://localhost:8080/client/api?command=samlsso", idps[0]);
+ resp.sendRedirect(redirectUrl);
+ return "";
+ } else {
+ final String samlResponse = ((String[])params.get("SAMLResponse"))[0];
+ Response processedSAMLResponse = processSAMLResponse(samlResponse);
+ String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
+ if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "Identity Provider send a non-successful authentication status code",
+ params, responseType));
+ }
+
+ Signature sig = processedSAMLResponse.getSignature();
+ //SignatureValidator validator = new SignatureValidator(credential);
+ //validator.validate(sig);
+
+ String uniqueUserId = null;
+ String accountName = "admin"; //GET from config, try, fail
+ Long domainId = 1L; // GET from config, try, fail
+ String username = null;
+ String password = "";
+ String firstName = "";
+ String lastName = "";
+ String timeZone = "";
+ String email = "";
+
+ Assertion assertion = processedSAMLResponse.getAssertions().get(0);
+ NameID nameId = assertion.getSubject().getNameID();
+
+ if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL)) {
+ username = nameId.getValue();
+ uniqueUserId = "saml-" + username;
+ if (nameId.getFormat().equals(NameIDType.EMAIL)) {
+ email = username;
+ }
+ }
+
+ String issuer = assertion.getIssuer().getValue();
+ String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();
+ AttributeStatement attributeStatement = assertion.getAttributeStatements().get(0);
+ List<Attribute> attributes = attributeStatement.getAttributes();
+
+ // Try capturing standard LDAP attributes
+ for (Attribute attribute: attributes) {
+ String attributeName = attribute.getName();
+ String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent();
+ if (attributeName.equalsIgnoreCase("uid") && uniqueUserId == null) {
+ username = attributeValue;
+ uniqueUserId = "saml-" + username;
+ } else if (attributeName.equalsIgnoreCase("givenName")) {
+ firstName = attributeValue;
+ } else if (attributeName.equalsIgnoreCase(("sn"))) {
+ lastName = attributeValue;
+ } else if (attributeName.equalsIgnoreCase("mail")) {
+ email = attributeValue;
+ }
+ }
+
+ User user = _entityMgr.findByUuid(User.class, uniqueUserId);
+ if (user == null && uniqueUserId != null && username != null
+ && accountName != null && domainId != null) {
+ CallContext.current().setEventDetails("UserName: " + username + ", FirstName :" + password + ", LastName: " + lastName);
+ user = _accountService.createUser(username, password, firstName, lastName, email, timeZone, accountName, domainId, uniqueUserId);
+ }
+
+ if (user != null) {
+ try {
+ if (_apiServer.verifyUser(user.getId())) {
+ LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, username, user.getPassword(), domainId, null, remoteAddress, params);
+ resp.addCookie(new Cookie("userid", loginResponse.getUserId()));
+ resp.addCookie(new Cookie("domainid", loginResponse.getDomainId()));
+ resp.addCookie(new Cookie("role", loginResponse.getType()));
+ resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8)));
+ resp.addCookie(new Cookie("sessionKey", URLEncoder.encode(loginResponse.getSessionKey(), HttpUtils.UTF_8)));
+ resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8)));
+ resp.addCookie(new Cookie("timezone", URLEncoder.encode(loginResponse.getTimeZone(), HttpUtils.UTF_8)));
+ resp.addCookie(new Cookie("userfullname", loginResponse.getFirstName() + "%20" + loginResponse.getLastName()));
+ resp.sendRedirect("http://localhost:8080/client");
+ return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
+
+ }
+ } catch (final CloudAuthenticationException ignored) {
+ }
+ }
+ }
+ } catch (IOException e) {
+ auditTrailSb.append("SP initiated SAML authentication using HTTP redirection failed:");
+ auditTrailSb.append(e.getMessage());
+ }
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "Unable to authenticate or retrieve user while performing SAML based SSO",
+ params, responseType));
+ }
+
+ @Override
+ public APIAuthenticationType getAPIType() {
+ return APIAuthenticationType.LOGIN_API;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
new file mode 100644
index 0000000..b82f2c8
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/SAML2LogoutAPIAuthenticatorCmd.java
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.api.command;
+
+import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.auth.APIAuthenticationType;
+import org.apache.cloudstack.api.auth.APIAuthenticator;
+import org.apache.cloudstack.api.response.LogoutCmdResponse;
+import org.apache.log4j.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.util.Map;
+
+@APICommand(name = "samlslo", description = "SAML Single Log Out API", responseObject = LogoutCmdResponse.class, entityType = {})
+public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
+ public static final Logger s_logger = Logger.getLogger(SAML2LogoutAPIAuthenticatorCmd.class.getName());
+ private static final String s_name = "logoutresponse";
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return Account.ACCOUNT_TYPE_NORMAL;
+ }
+
+ @Override
+ public void execute() throws ServerApiException {
+ // We should never reach here
+ throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
+ }
+
+ @Override
+ public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
+ auditTrailSb.append("=== Logging out ===");
+ // TODO: check global config and do either local or global log out
+ LogoutCmdResponse response = new LogoutCmdResponse();
+ response.setDescription("success");
+ response.setResponseName(getCommandName());
+ return ApiResponseSerializer.toSerializedString(response, responseType);
+ }
+
+ @Override
+ public APIAuthenticationType getAPIType() {
+ return APIAuthenticationType.LOGOUT_API;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
new file mode 100644
index 0000000..1a37f65
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package org.apache.cloudstack.saml;
+
+import com.cloud.server.auth.DefaultUserAuthenticator;
+import com.cloud.server.auth.UserAuthenticator;
+import com.cloud.user.User;
+import com.cloud.user.UserAccount;
+import com.cloud.user.dao.UserAccountDao;
+import com.cloud.user.dao.UserDao;
+import com.cloud.utils.Pair;
+import org.apache.log4j.Logger;
+
+import javax.ejb.Local;
+import javax.inject.Inject;
+import java.util.Map;
+
+@Local(value = {UserAuthenticator.class})
+public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
+ public static final Logger s_logger = Logger.getLogger(SAML2UserAuthenticator.class);
+
+ @Inject
+ private UserAccountDao _userAccountDao;
+ @Inject
+ private UserDao _userDao;
+
+ @Override
+ public Pair<Boolean, ActionOnFailedAuthentication> authenticate(String username, String password, Long domainId, Map<String, Object[]> requestParameters) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Trying SAML2 auth for user: " + username);
+ }
+ final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
+ if (userAccount == null) {
+ s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
+ return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
+ } else {
+ User user = _userDao.getUser(userAccount.getId());
+ // TODO: check SAMLRequest, signature etc. from requestParameters
+ if (user != null && user.getUuid().startsWith("saml")) {
+ return new Pair<Boolean, ActionOnFailedAuthentication>(true, null);
+ }
+ }
+ // Deny all by default
+ return new Pair<Boolean, ActionOnFailedAuthentication>(false, ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
+ }
+
+ @Override
+ public String encode(final String password) {
+ // TODO: Complete method
+ StringBuilder sb = new StringBuilder(32);
+ return sb.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/ApiServerService.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/ApiServerService.java b/server/src/com/cloud/api/ApiServerService.java
deleted file mode 100644
index aa3b8f7..0000000
--- a/server/src/com/cloud/api/ApiServerService.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.api;
-
-import com.cloud.exception.CloudAuthenticationException;
-import org.apache.cloudstack.api.ResponseObject;
-import org.apache.cloudstack.api.ServerApiException;
-
-import javax.servlet.http.HttpSession;
-import java.util.Map;
-
-public interface ApiServerService {
- public boolean verifyRequest(Map<String, Object[]> requestParameters, Long userId) throws ServerApiException;
-
- public Long fetchDomainId(String domainUUID);
-
- public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, String loginIpAddress,
- Map<String, Object[]> requestParameters) throws CloudAuthenticationException;
-
- public void logoutUser(long userId);
-
- public boolean verifyUser(Long userId);
-
- public String getSerializedApiError(int errorCode, String errorText, Map<String, Object[]> apiCommandParams, String responseType);
-
- public String getSerializedApiError(ServerApiException ex, Map<String, Object[]> apiCommandParams, String responseType);
-
- public String handleRequest(Map params, String responseType, StringBuilder auditTrailSb) throws ServerApiException;
-
- public Class<?> getCmdClass(String cmdName);
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/auth/APIAuthenticationManager.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/APIAuthenticationManager.java b/server/src/com/cloud/api/auth/APIAuthenticationManager.java
deleted file mode 100644
index 7fd4da9..0000000
--- a/server/src/com/cloud/api/auth/APIAuthenticationManager.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package com.cloud.api.auth;
-
-import com.cloud.utils.component.PluggableService;
-
-public interface APIAuthenticationManager extends PluggableService {
- public APIAuthenticator getAPIAuthenticator(String name);
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/auth/APIAuthenticationType.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/APIAuthenticationType.java b/server/src/com/cloud/api/auth/APIAuthenticationType.java
deleted file mode 100644
index bdd37ec..0000000
--- a/server/src/com/cloud/api/auth/APIAuthenticationType.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.api.auth;
-
-public enum APIAuthenticationType {
- LOGIN_API, LOGOUT_API
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/auth/APIAuthenticator.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/APIAuthenticator.java b/server/src/com/cloud/api/auth/APIAuthenticator.java
deleted file mode 100644
index 90cd7ec..0000000
--- a/server/src/com/cloud/api/auth/APIAuthenticator.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.api.auth;
-
-import org.apache.cloudstack.api.ServerApiException;
-
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.util.Map;
-
-/*
-* APIAuthenticator is an interface that defines method that
-* a class should implement that help with Authentication and accepts
-* a command string and an array of parameters. This should be used only
-* in the Servlet that is doing authentication.
-*
-* @param command The API command name such as login, logout etc
-* @param params An array of HTTP parameters
-* @param session HttpSession object
-* */
-public interface APIAuthenticator {
- public String authenticate(String command, Map<String, Object[]> params,
- HttpSession session, String remoteAddress, String responseType,
- StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException;
- public APIAuthenticationType getAPIType();
-
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java b/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
deleted file mode 100644
index ce97cfd..0000000
--- a/server/src/com/cloud/api/auth/SAML2LoginAPIAuthenticatorCmd.java
+++ /dev/null
@@ -1,350 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package com.cloud.api.auth;
-
-import com.cloud.api.ApiServerService;
-import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.exception.CloudAuthenticationException;
-import com.cloud.user.Account;
-import com.cloud.user.User;
-import com.cloud.utils.HttpUtils;
-import com.cloud.utils.db.EntityManager;
-import org.apache.cloudstack.api.APICommand;
-import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.ApiErrorCode;
-import org.apache.cloudstack.api.BaseCmd;
-import org.apache.cloudstack.api.Parameter;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.response.LoginCmdResponse;
-import org.apache.cloudstack.context.CallContext;
-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.Assertion;
-import org.opensaml.saml2.core.Attribute;
-import org.opensaml.saml2.core.AttributeStatement;
-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.NameID;
-import org.opensaml.saml2.core.NameIDPolicy;
-import org.opensaml.saml2.core.NameIDType;
-import org.opensaml.saml2.core.RequestedAuthnContext;
-import org.opensaml.saml2.core.Response;
-import org.opensaml.saml2.core.StatusCode;
-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.signature.Signature;
-import org.opensaml.xml.util.Base64;
-import org.opensaml.xml.util.XMLHelper;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.xml.sax.SAXException;
-
-import javax.inject.Inject;
-import javax.servlet.http.Cookie;
-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.List;
-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 {
- public static final Logger s_logger = Logger.getLogger(SAML2LoginAPIAuthenticatorCmd.class.getName());
- private static final String s_name = "loginresponse";
-
- /////////////////////////////////////////////////////
- //////////////// API parameters /////////////////////
- /////////////////////////////////////////////////////
- @Parameter(name = ApiConstants.IDP_URL, type = CommandType.STRING, description = "Identity Provider SSO HTTP-Redirect binding URL", required = true)
- private String idpUrl;
-
- @Inject
- ApiServerService _apiServer;
- @Inject
- EntityManager _entityMgr;
-
- /////////////////////////////////////////////////////
- /////////////////// Accessors ///////////////////////
- /////////////////////////////////////////////////////
-
- public String getIdpUrl() {
- return idpUrl;
- }
-
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
-
- @Override
- public String getCommandName() {
- return s_name;
- }
-
- @Override
- public long getEntityOwnerId() {
- return Account.ACCOUNT_TYPE_NORMAL;
- }
-
- @Override
- public void execute() throws ServerApiException {
- // We should never reach here
- throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
- }
-
- public String buildAuthnRequestUrl(String consumerUrl, String identityProviderUrl) {
- String randomId = new BigInteger(130, new SecureRandom()).toString(32);
- String spId = "org.apache.cloudstack";
- String redirectUrl = "";
- try {
- DefaultBootstrap.bootstrap();
- AuthnRequest authnRequest = this.buildAuthnRequestObject(randomId, spId, identityProviderUrl, consumerUrl);
- redirectUrl = identityProviderUrl + "?SAMLRequest=" + encodeAuthnRequest(authnRequest);
- } catch (ConfigurationException | FactoryConfigurationError | MarshallingException | IOException e) {
- s_logger.error("SAML AuthnRequest message building error: " + e.getMessage());
- }
- return redirectUrl;
- }
-
- private AuthnRequest buildAuthnRequestObject(String authnId, String spId, String idpUrl, String consumerUrl) {
- // Issuer object
- IssuerBuilder issuerBuilder = new IssuerBuilder();
- Issuer issuer = issuerBuilder.buildObject();
- issuer.setValue(spId);
-
- // NameIDPolicy
- NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
- NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
- nameIdPolicy.setFormat(NameIDType.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.MINIMUM);
- requestedAuthnContext.getAuthnContextClassRefs().add(
- authnContextClassRef);
-
- // Creation of AuthRequestObject
- AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
- AuthnRequest authnRequest = authRequestBuilder.buildObject();
- 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); //SAML2_ARTIFACT_BINDING_URI);
- authnRequest.setAssertionConsumerServiceURL(consumerUrl);
- authnRequest.setNameIDPolicy(nameIdPolicy);
- authnRequest.setRequestedAuthnContext(requestedAuthnContext);
-
- return authnRequest;
- }
-
- private String encodeAuthnRequest(AuthnRequest authnRequest)
- throws MarshallingException, IOException {
- Marshaller marshaller = Configuration.getMarshallerFactory()
- .getMarshaller(authnRequest);
- Element authDOM = marshaller.marshall(authnRequest);
- StringWriter requestWriter = new StringWriter();
- XMLHelper.writeNode(authDOM, requestWriter);
- String requestMessage = requestWriter.toString();
- Deflater deflater = new Deflater(Deflater.DEFLATED, true);
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
- deflaterOutputStream.write(requestMessage.getBytes());
- deflaterOutputStream.close();
- String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(), Base64.DONT_BREAK_LINES);
- encodedRequestMessage = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();
- return encodedRequestMessage;
- }
-
- public Response processSAMLResponse(String responseMessage) {
- XMLObject responseObject = null;
- try {
- responseObject = this.unmarshall(responseMessage);
-
- } catch (ConfigurationException | ParserConfigurationException | SAXException | IOException | UnmarshallingException e) {
- s_logger.error("SAMLResponse processing error: " + e.getMessage());
- }
- return (Response) responseObject;
- }
-
- private XMLObject unmarshall(String responseMessage)
- throws ConfigurationException, ParserConfigurationException,
- SAXException, IOException, UnmarshallingException {
- try {
- DefaultBootstrap.bootstrap();
- } catch (ConfigurationException | FactoryConfigurationError e) {
- s_logger.error("SAML response message decoding error: " + e.getMessage());
- }
- DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
- documentBuilderFactory.setNamespaceAware(true);
- DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
- byte[] base64DecodedResponse = Base64.decode(responseMessage);
- Document document = docBuilder.parse(new ByteArrayInputStream(base64DecodedResponse));
- Element element = document.getDocumentElement();
- UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
- Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
- return unmarshaller.unmarshall(element);
- }
-
- @Override
- public String authenticate(final String command, final Map<String, Object[]> params, final HttpSession session, final String remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
- try {
- if (!params.containsKey("SAMLResponse")) {
- final String[] idps = (String[])params.get("idpurl");
- String redirectUrl = buildAuthnRequestUrl("http://localhost:8080/client/api?command=samlsso", idps[0]);
- resp.sendRedirect(redirectUrl);
- return "";
- } else {
- final String samlResponse = ((String[])params.get("SAMLResponse"))[0];
- Response processedSAMLResponse = processSAMLResponse(samlResponse);
- String statusCode = processedSAMLResponse.getStatus().getStatusCode().getValue();
- if (!statusCode.equals(StatusCode.SUCCESS_URI)) {
- throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
- "Identity Provider send a non-successful authentication status code",
- params, responseType));
- }
-
- Signature sig = processedSAMLResponse.getSignature();
- //SignatureValidator validator = new SignatureValidator(credential);
- //validator.validate(sig);
-
- String uniqueUserId = null;
- String accountName = "admin"; //GET from config, try, fail
- Long domainId = 1L; // GET from config, try, fail
- String username = null;
- String password = "";
- String firstName = "";
- String lastName = "";
- String timeZone = "";
- String email = "";
-
- Assertion assertion = processedSAMLResponse.getAssertions().get(0);
- NameID nameId = assertion.getSubject().getNameID();
-
- if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL)) {
- username = nameId.getValue();
- uniqueUserId = "saml-" + username;
- if (nameId.getFormat().equals(NameIDType.EMAIL)) {
- email = username;
- }
- }
-
- String issuer = assertion.getIssuer().getValue();
- String audience = assertion.getConditions().getAudienceRestrictions().get(0).getAudiences().get(0).getAudienceURI();
- AttributeStatement attributeStatement = assertion.getAttributeStatements().get(0);
- List<Attribute> attributes = attributeStatement.getAttributes();
-
- // Try capturing standard LDAP attributes
- for (Attribute attribute: attributes) {
- String attributeName = attribute.getName();
- String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent();
- if (attributeName.equalsIgnoreCase("uid") && uniqueUserId == null) {
- username = attributeValue;
- uniqueUserId = "saml-" + username;
- } else if (attributeName.equalsIgnoreCase("givenName")) {
- firstName = attributeValue;
- } else if (attributeName.equalsIgnoreCase(("sn"))) {
- lastName = attributeValue;
- } else if (attributeName.equalsIgnoreCase("mail")) {
- email = attributeValue;
- }
- }
-
- User user = _entityMgr.findByUuid(User.class, uniqueUserId);
- if (user == null && uniqueUserId != null && username != null
- && accountName != null && domainId != null) {
- CallContext.current().setEventDetails("UserName: " + username + ", FirstName :" + password + ", LastName: " + lastName);
- user = _accountService.createUser(username, password, firstName, lastName, email, timeZone, accountName, domainId, uniqueUserId);
- }
-
- if (user != null) {
- try {
- if (_apiServer.verifyUser(user.getId())) {
- LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, username, user.getPassword(), domainId, null, remoteAddress, params);
- resp.addCookie(new Cookie("userid", loginResponse.getUserId()));
- resp.addCookie(new Cookie("domainid", loginResponse.getDomainId()));
- resp.addCookie(new Cookie("role", loginResponse.getType()));
- resp.addCookie(new Cookie("username", URLEncoder.encode(loginResponse.getUsername(), HttpUtils.UTF_8)));
- resp.addCookie(new Cookie("sessionKey", URLEncoder.encode(loginResponse.getSessionKey(), HttpUtils.UTF_8)));
- resp.addCookie(new Cookie("account", URLEncoder.encode(loginResponse.getAccount(), HttpUtils.UTF_8)));
- resp.addCookie(new Cookie("timezone", URLEncoder.encode(loginResponse.getTimeZone(), HttpUtils.UTF_8)));
- resp.addCookie(new Cookie("userfullname", loginResponse.getFirstName() + "%20" + loginResponse.getLastName()));
- resp.sendRedirect("http://localhost:8080/client");
- return ApiResponseSerializer.toSerializedString(loginResponse, responseType);
-
- }
- } catch (final CloudAuthenticationException ignored) {
- }
- }
- }
- } catch (IOException e) {
- auditTrailSb.append("SP initiated SAML authentication using HTTP redirection failed:");
- auditTrailSb.append(e.getMessage());
- }
- throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
- "Unable to authenticate or retrieve user while performing SAML based SSO",
- params, responseType));
- }
-
- @Override
- public APIAuthenticationType getAPIType() {
- return APIAuthenticationType.LOGIN_API;
- }
-}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f1d85834/server/src/com/cloud/api/auth/SAML2LogoutAPIAuthenticatorCmd.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/auth/SAML2LogoutAPIAuthenticatorCmd.java b/server/src/com/cloud/api/auth/SAML2LogoutAPIAuthenticatorCmd.java
deleted file mode 100644
index 9119588..0000000
--- a/server/src/com/cloud/api/auth/SAML2LogoutAPIAuthenticatorCmd.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-package com.cloud.api.auth;
-
-import com.cloud.api.response.ApiResponseSerializer;
-import com.cloud.user.Account;
-import org.apache.cloudstack.api.APICommand;
-import org.apache.cloudstack.api.ApiErrorCode;
-import org.apache.cloudstack.api.BaseCmd;
-import org.apache.cloudstack.api.ServerApiException;
-import org.apache.cloudstack.api.response.LogoutCmdResponse;
-import org.apache.log4j.Logger;
-
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.util.Map;
-
-@APICommand(name = "samlslo", description = "SAML Single Log Out API", responseObject = LogoutCmdResponse.class, entityType = {})
-public class SAML2LogoutAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
- public static final Logger s_logger = Logger.getLogger(SAML2LogoutAPIAuthenticatorCmd.class.getName());
- private static final String s_name = "logoutresponse";
-
- /////////////////////////////////////////////////////
- /////////////// API Implementation///////////////////
- /////////////////////////////////////////////////////
-
- @Override
- public String getCommandName() {
- return s_name;
- }
-
- @Override
- public long getEntityOwnerId() {
- return Account.ACCOUNT_TYPE_NORMAL;
- }
-
- @Override
- public void execute() throws ServerApiException {
- // We should never reach here
- throw new ServerApiException(ApiErrorCode.METHOD_NOT_ALLOWED, "This is an authentication api, cannot be used directly");
- }
-
- @Override
- public String authenticate(String command, Map<String, Object[]> params, HttpSession session, String remoteAddress, String responseType, StringBuilder auditTrailSb, final HttpServletResponse resp) throws ServerApiException {
- auditTrailSb.append("=== Logging out ===");
- // TODO: check global config and do either local or global log out
- LogoutCmdResponse response = new LogoutCmdResponse();
- response.setDescription("success");
- response.setResponseName(getCommandName());
- return ApiResponseSerializer.toSerializedString(response, responseType);
- }
-
- @Override
- public APIAuthenticationType getAPIType() {
- return APIAuthenticationType.LOGOUT_API;
- }
-}