diff --git a/plugins/connectors/cloudian/src/com/cloudian/cloudstack/CloudianConnectorImpl.java b/plugins/connectors/cloudian/src/com/cloudian/cloudstack/CloudianConnectorImpl.java index 30118df958f..6ad289c21f6 100644 --- a/plugins/connectors/cloudian/src/com/cloudian/cloudstack/CloudianConnectorImpl.java +++ b/plugins/connectors/cloudian/src/com/cloudian/cloudstack/CloudianConnectorImpl.java @@ -17,10 +17,16 @@ package com.cloudian.cloudstack; +import static com.amazonaws.services.s3.internal.Constants.HMAC_SHA1_ALGORITHM; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -28,6 +34,7 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageSubscriber; +import org.apache.commons.codec.binary.Base64; import org.apache.log4j.Logger; import com.cloud.domain.Domain; @@ -40,6 +47,7 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.component.ComponentLifecycleBase; import com.cloudian.cloudstack.api.CloudianIsEnabledCmd; import com.cloudian.cloudstack.api.CloudianSsoLoginCmd; +import com.google.common.base.Strings; public class CloudianConnectorImpl extends ComponentLifecycleBase implements CloudianConnector, Configurable { private static final Logger LOG = Logger.getLogger(CloudianConnectorImpl.class); @@ -58,11 +66,74 @@ public class CloudianConnectorImpl extends ComponentLifecycleBase implements Clo return !CloudianConnectorEnabled.value(); } + /** + * Computes RFC 2104-compliant HMAC signature. + * + * @param data The data to be signed. + * @param key The signing key. + * @return The Base64-encoded RFC 2104-compliant HMAC signature. + */ + public static String calculateRFC2104HMAC(String data, String key) { + try { + // get an hmac_sha1 key from the raw key bytes + SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM); + + // get an hmac_sha1 Mac instance and initialize with the signing key + Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); + mac.init(signingKey); + + // compute the hmac on input data bytes + byte[] rawHmac = mac.doFinal(data.getBytes()); + + // return the base64-encode the hmac + return Base64.encodeBase64String(rawHmac); + } catch (Exception e) { + // LOG? + } + return null; + } + + public static String genSSOParams(String userId, String groupId, String key) { + StringBuilder sts = new StringBuilder(); + sts.append("user="); + sts.append(userId); + sts.append("&group="); + sts.append(groupId); + sts.append("×tamp="); + sts.append(System.currentTimeMillis()); + + String signature = calculateRFC2104HMAC(sts.toString(), key); + if (Strings.isNullOrEmpty(signature)) { + return null; + } + sts.append("&signature="); + try { + sts.append(URLEncoder.encode(signature, "UTF-8")); + } catch (UnsupportedEncodingException e) { + return null; + } + + sts.append("&redirect="); + if (groupId.equals("0")) { + sts.append("admin.htm"); + } else { + sts.append("explorer.htm"); + } + + return sts.toString(); + } + @Override public String generateSsoUrl() { // add user/group in CMC if not available // return generated login url using sso shared key - return "https://cmc.hs.yadav.xyz:8443/Cloudian/ssosecurelogin.htm?user=admin&group=0×tamp=1505293165230&signature=UbSDimMwSHkKrWgPDrdUpruA%2FGA%3D&redirect=admin.htm"; + + String ssoparams = genSSOParams("admin", "0", CloudianSsoKey.value()); + if (ssoparams == null) { + return null; + } + + return "https://cmc.hs.yadav.xyz:8443/Cloudian/ssosecurelogin.htm?" + ssoparams; } @Override diff --git a/plugins/connectors/cloudian/src/com/cloudian/cloudstack/api/CloudianSsoLoginCmd.java b/plugins/connectors/cloudian/src/com/cloudian/cloudstack/api/CloudianSsoLoginCmd.java index af330ca6bcf..8c0a676accc 100644 --- a/plugins/connectors/cloudian/src/com/cloudian/cloudstack/api/CloudianSsoLoginCmd.java +++ b/plugins/connectors/cloudian/src/com/cloudian/cloudstack/api/CloudianSsoLoginCmd.java @@ -21,11 +21,14 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; 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 com.cloud.user.Account; import com.cloudian.cloudstack.CloudianConnector; import com.cloudian.cloudstack.response.CloudianSsoLoginResponse; +import com.google.common.base.Strings; @APICommand(name = CloudianSsoLoginCmd.APINAME, description = "Generates single-sign-on login url for logged-in CloudStack user to access the Cloudian Management Console", responseObject = CloudianSsoLoginResponse.class, @@ -54,8 +57,12 @@ public class CloudianSsoLoginCmd extends BaseCmd { @Override public void execute() { + final String ssoUrl = connector.generateSsoUrl(); + if (Strings.isNullOrEmpty(ssoUrl)) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate Cloudian single-sign on URL for the user"); + } final CloudianSsoLoginResponse response = new CloudianSsoLoginResponse(); - response.setSsoRedirectUrl(connector.generateSsoUrl()); + response.setSsoRedirectUrl(ssoUrl); response.setResponseName(getCommandName()); response.setObjectName(APINAME.toLowerCase()); setResponseObject(response);