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 2015/06/11 17:26:42 UTC
[1/2] git commit: updated refs/heads/saml-production-grade to 45cd42b
Repository: cloudstack
Updated Branches:
refs/heads/saml-production-grade 645f75bb0 -> 45cd42b1a (forced update)
CLOUDSTACK-8462: SAML users need to be authorized before they can authenticate
- New column entity to track saml entity id for a user
- Reusing source column to check if user is saml enabled or not
- Add new source types, saml2 and saml2disabled
- New table saml_token to solve the issue of multiple users across domains and
to enforce security by tracking authn token and checking the samlresponse for
the tokens
- Implement API: authorizeSamlSso to enable/disable saml authentication for a
user
- Stubs to implement saml token flushing/expiry
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/138da3c5
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/138da3c5
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/138da3c5
Branch: refs/heads/saml-production-grade
Commit: 138da3c507eb4d63b297f06845fbf293f439a3ff
Parents: d3f43fd
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Mon Jun 8 19:18:06 2015 +0200
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Jun 11 18:25:33 2015 +0300
----------------------------------------------------------------------
api/src/com/cloud/user/User.java | 7 +-
api/src/com/cloud/user/UserAccount.java | 4 +
.../org/apache/cloudstack/api/ApiConstants.java | 1 +
client/tomcatconf/commands.properties.in | 1 +
.../src/com/cloud/user/UserAccountVO.java | 11 ++
engine/schema/src/com/cloud/user/UserVO.java | 10 ++
.../cloudstack/saml2/spring-saml2-context.xml | 3 +
.../api/command/AuthorizeSAMLSSOCmd.java | 79 ++++++++++++++
.../command/SAML2LoginAPIAuthenticatorCmd.java | 52 ++++++---
.../cloudstack/saml/SAML2AuthManager.java | 10 +-
.../cloudstack/saml/SAML2AuthManagerImpl.java | 108 ++++++++++++++++---
.../cloudstack/saml/SAML2UserAuthenticator.java | 4 +-
.../apache/cloudstack/saml/SAMLTokenDao.java | 23 ++++
.../cloudstack/saml/SAMLTokenDaoImpl.java | 43 ++++++++
.../org/apache/cloudstack/saml/SAMLTokenVO.java | 97 +++++++++++++++++
setup/db/db/schema-451to452.sql | 13 +++
16 files changed, 436 insertions(+), 30 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/api/src/com/cloud/user/User.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/user/User.java b/api/src/com/cloud/user/User.java
index c9d91d7..1f0dcfd 100644
--- a/api/src/com/cloud/user/User.java
+++ b/api/src/com/cloud/user/User.java
@@ -23,7 +23,7 @@ import org.apache.cloudstack.api.InternalIdentity;
public interface User extends OwnedBy, InternalIdentity {
public enum Source {
- LDAP, SAML2, UNKNOWN
+ LDAP, SAML2, SAML2DISABLED, UNKNOWN
}
public static final long UID_SYSTEM = 1;
@@ -83,4 +83,9 @@ public interface User extends OwnedBy, InternalIdentity {
public Source getSource();
+ void setSource(Source source);
+
+ public String getExternalEntity();
+
+ public void setExternalEntity(String entity);
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/api/src/com/cloud/user/UserAccount.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/user/UserAccount.java b/api/src/com/cloud/user/UserAccount.java
index d44fcf7..0449514 100644
--- a/api/src/com/cloud/user/UserAccount.java
+++ b/api/src/com/cloud/user/UserAccount.java
@@ -63,4 +63,8 @@ public interface UserAccount extends InternalIdentity {
int getLoginAttempts();
public User.Source getSource();
+
+ public String getExternalEntity();
+
+ public void setExternalEntity(String entity);
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index 1b5e0cb..2471e08 100755
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -373,6 +373,7 @@ public class ApiConstants {
public static final String ISOLATION_METHODS = "isolationmethods";
public static final String PHYSICAL_NETWORK_ID = "physicalnetworkid";
public static final String DEST_PHYSICAL_NETWORK_ID = "destinationphysicalnetworkid";
+ public static final String ENABLE = "enable";
public static final String ENABLED = "enabled";
public static final String SERVICE_NAME = "servicename";
public static final String DHCP_RANGE = "dhcprange";
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index 2cd99c5..094cd76 100644
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@ -27,6 +27,7 @@ samlSso=15
samlSlo=15
getSPMetadata=15
listIdps=15
+authorizeSamlSso=1
### Account commands
createAccount=7
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/engine/schema/src/com/cloud/user/UserAccountVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/UserAccountVO.java b/engine/schema/src/com/cloud/user/UserAccountVO.java
index 5f33c47..80ee873 100644
--- a/engine/schema/src/com/cloud/user/UserAccountVO.java
+++ b/engine/schema/src/com/cloud/user/UserAccountVO.java
@@ -105,6 +105,9 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
@Enumerated(value = EnumType.STRING)
private User.Source source;
+ @Column(name = "external_entity", length = 65535)
+ private String externalEntity = null;
+
public UserAccountVO() {
}
@@ -296,4 +299,12 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
public void setSource(User.Source source) {
this.source = source;
}
+
+ public String getExternalEntity() {
+ return externalEntity;
+ }
+
+ public void setExternalEntity(String externalEntity) {
+ this.externalEntity = externalEntity;
+ }
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/engine/schema/src/com/cloud/user/UserVO.java
----------------------------------------------------------------------
diff --git a/engine/schema/src/com/cloud/user/UserVO.java b/engine/schema/src/com/cloud/user/UserVO.java
index eb2813b..da7811e 100644
--- a/engine/schema/src/com/cloud/user/UserVO.java
+++ b/engine/schema/src/com/cloud/user/UserVO.java
@@ -101,6 +101,9 @@ public class UserVO implements User, Identity, InternalIdentity {
@Enumerated(value = EnumType.STRING)
private Source source;
+ @Column(name = "external_entity", length = 65535)
+ private String externalEntity;
+
public UserVO() {
this.uuid = UUID.randomUUID().toString();
}
@@ -283,4 +286,11 @@ public class UserVO implements User, Identity, InternalIdentity {
this.source = source;
}
+ public String getExternalEntity() {
+ return externalEntity;
+ }
+
+ public void setExternalEntity(String externalEntity) {
+ this.externalEntity = externalEntity;
+ }
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
index 92f89b8..d3a2194 100644
--- a/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
+++ b/plugins/user-authenticators/saml2/resources/META-INF/cloudstack/saml2/spring-saml2-context.xml
@@ -33,4 +33,7 @@
<property name="name" value="SAML2Auth"/>
</bean>
+ <bean id="samlTokenDao" class="org.apache.cloudstack.saml.SAMLTokenDaoImpl">
+ </bean>
+
</beans>
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
new file mode 100644
index 0000000..290a440
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/api/command/AuthorizeSAMLSSOCmd.java
@@ -0,0 +1,79 @@
+package org.apache.cloudstack.api.command;
+
+import com.cloud.user.Account;
+import com.cloud.user.User;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.response.IdpResponse;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.api.response.UserResponse;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.saml.SAML2AuthManager;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+@APICommand(name = "authorizeSamlSso", description = "Allow or disallow a user to use SAML SSO", responseObject = SuccessResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = true)
+public class AuthorizeSAMLSSOCmd extends BaseCmd {
+ public static final Logger s_logger = Logger.getLogger(AuthorizeSAMLSSOCmd.class.getName());
+
+ private static final String s_name = "authorizesamlssoresponse";
+
+ @Inject
+ SAML2AuthManager _samlAuthManager;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "User uuid")
+ private Long id;
+
+ @Parameter(name = ApiConstants.ENABLE, type = CommandType.BOOLEAN, required = true, description = "If true, authorizes user to be able to use SAML for Single Sign. If False, disable user to user SAML SSO.")
+ private Boolean enable;
+
+ public Boolean getEnable() {
+ return enable;
+ }
+
+ public String getEntityId() {
+ return entityId;
+ }
+
+ @Parameter(name = ApiConstants.ENTITY_ID, type = CommandType.STRING, entityType = IdpResponse.class, description = "The Identity Provider ID the user is allowed to get single signed on from")
+ private String entityId;
+
+ public Long getId() {
+ return id;
+ }
+
+ @Override
+ public String getCommandName() {
+ return s_name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ User user = _entityMgr.findById(User.class, getId());
+ if (user != null) {
+ return user.getAccountId();
+ }
+
+ return Account.ACCOUNT_ID_SYSTEM;
+ }
+
+ @Override
+ public void execute() {
+ CallContext.current().setEventDetails("UserId: " + getId());
+ SuccessResponse response = new SuccessResponse();
+ Boolean status = false;
+ if (_samlAuthManager.authorizeUser(getId(), getEntityId(), getEnable())) {
+ status = true;
+ }
+ response.setResponseName(getCommandName());
+ response.setSuccess(status);
+ setResponseObject(response);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/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
index 631aed2..0d935ff 100644
--- 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
@@ -23,7 +23,6 @@ import com.cloud.exception.CloudAuthenticationException;
import com.cloud.user.Account;
import com.cloud.user.DomainManager;
import com.cloud.user.UserAccount;
-import com.cloud.user.UserAccountVO;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.db.EntityManager;
@@ -41,6 +40,7 @@ import org.apache.cloudstack.api.response.LoginCmdResponse;
import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.saml.SAMLPluginConstants;
import org.apache.cloudstack.saml.SAMLProviderMetadata;
+import org.apache.cloudstack.saml.SAMLTokenVO;
import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.log4j.Logger;
import org.opensaml.DefaultBootstrap;
@@ -141,12 +141,18 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
@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 HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException {
try {
- if (!params.containsKey("SAMLResponse") && !params.containsKey("SAMLart")) {
+ if (!params.containsKey(SAMLPluginConstants.SAML_RESPONSE) && !params.containsKey("SAMLart")) {
String idpId = null;
- final String[] idps = (String[])params.get(ApiConstants.IDP_ID);
- if (idps != null && idps.length > 0) {
- idpId = idps[0];
+ String domainPath = null;
+
+ if (params.containsKey(ApiConstants.IDP_ID)) {
+ idpId = ((String[])params.get(ApiConstants.IDP_ID))[0];
+ }
+
+ if (params.containsKey(ApiConstants.DOMAIN)) {
+ domainPath = ((String[])params.get(ApiConstants.DOMAIN))[0];
}
+
SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(idpId);
if (idpMetadata == null) {
@@ -161,6 +167,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
params, responseType));
}
String authnId = SAMLUtils.generateSecureRandomId();
+ _samlAuthManager.saveToken(authnId, domainPath, idpMetadata.getEntityId());
s_logger.debug("Sending SAMLRequest id=" + authnId);
String redirectUrl = SAMLUtils.buildAuthnRequestUrl(authnId, spMetadata, idpMetadata, SAML2AuthManager.SAMLSignatureAlgorithm.value());
resp.sendRedirect(redirectUrl);
@@ -195,13 +202,28 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
s_logger.error("The default domain ID for SAML users is not set correct, it should be a UUID. ROOT domain will be used.");
}
- String responseToId = processedSAMLResponse.getInResponseTo();
- s_logger.debug("Received SAMLResponse in response to id=" + responseToId);
-
Issuer issuer = processedSAMLResponse.getIssuer();
SAMLProviderMetadata spMetadata = _samlAuthManager.getSPMetadata();
SAMLProviderMetadata idpMetadata = _samlAuthManager.getIdPMetadata(issuer.getValue());
+ String responseToId = processedSAMLResponse.getInResponseTo();
+ s_logger.debug("Received SAMLResponse in response to id=" + responseToId);
+ SAMLTokenVO token = _samlAuthManager.getToken(responseToId);
+ if (token != null) {
+ if (token.getDomainId() != null) {
+ domainId = token.getDomainId();
+ }
+ if (!(token.getEntity().equalsIgnoreCase(issuer.getValue()))) {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "The SAML response contains Issuer Entity ID that is different from the original SAML request",
+ params, responseType));
+ }
+ } else {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "Received SAML response for a SSO request that we may not have made or has expired, please try logging in again",
+ params, responseType));
+ }
+
// Set IdpId for this session
session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue());
@@ -267,15 +289,19 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
"Failed to find admin configured username attribute in the SAML Response. Please ask your administrator to check SAML user attribute name.", params, responseType));
}
- List<UserAccountVO> userAccounts = _userAccountDao.getAllUsersByName(username);
- UserAccount userAccount = null;
- if (userAccounts != null && userAccounts.size() > 0) {
- userAccount = userAccounts.get(0);
+
+ UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
+ if (userAccount == null || userAccount.getExternalEntity() == null || !userAccount.getExternalEntity().equalsIgnoreCase(issuer.getValue())) {
+ throw new ServerApiException(ApiErrorCode.ACCOUNT_ERROR, _apiServer.getSerializedApiError(ApiErrorCode.ACCOUNT_ERROR.getHttpCode(),
+ "Your authenticated user is not authorized, please contact your administrator",
+ params, responseType));
}
+
if (userAccount != null) {
try {
if (_apiServer.verifyUser(userAccount.getId())) {
- LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, userAccount.getUsername(), userAccount.getPassword(), userAccount.getDomainId(), null, remoteAddress, params);
+ LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, userAccount.getUsername(), userAccount.getUsername() + userAccount.getSource().toString(),
+ userAccount.getDomainId(), null, remoteAddress, params);
resp.addCookie(new Cookie("userid", URLEncoder.encode(loginResponse.getUserId(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("domainid", URLEncoder.encode(loginResponse.getDomainId(), HttpUtils.UTF_8)));
resp.addCookie(new Cookie("role", URLEncoder.encode(loginResponse.getType(), HttpUtils.UTF_8)));
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
index 3b0998b..164050f 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManager.java
@@ -17,12 +17,13 @@
package org.apache.cloudstack.saml;
+import com.cloud.utils.component.PluggableService;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
import org.apache.cloudstack.framework.config.ConfigKey;
import java.util.Collection;
-public interface SAML2AuthManager extends PluggableAPIAuthenticator {
+public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService {
public static final ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
"Indicates whether SAML SSO plugin is enabled or not", true);
@@ -72,4 +73,11 @@ public interface SAML2AuthManager extends PluggableAPIAuthenticator {
public SAMLProviderMetadata getSPMetadata();
public SAMLProviderMetadata getIdPMetadata(String entityId);
public Collection<SAMLProviderMetadata> getAllIdPMetadata();
+
+ public boolean isUserAuthorized(Long userId, String entityId);
+ public boolean authorizeUser(Long userId, String entityId, boolean enable);
+
+ public void saveToken(String authnId, String domain, String entity);
+ public SAMLTokenVO getToken(String authnId);
+ public void expireTokens();
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
index 5c3137f..b1890e8 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java
@@ -16,8 +16,14 @@
// under the License.
package org.apache.cloudstack.saml;
+import com.cloud.domain.Domain;
+import com.cloud.user.DomainManager;
+import com.cloud.user.User;
+import com.cloud.user.UserVO;
+import com.cloud.user.dao.UserDao;
import com.cloud.utils.component.AdapterBase;
import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator;
+import org.apache.cloudstack.api.command.AuthorizeSAMLSSOCmd;
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
import org.apache.cloudstack.api.command.ListIdpsCmd;
import org.apache.cloudstack.api.command.SAML2LoginAPIAuthenticatorCmd;
@@ -91,6 +97,15 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
@Inject
private KeystoreDao _ksDao;
+ @Inject
+ private SAMLTokenDao _samlTokenDao;
+
+ @Inject
+ private UserDao _userDao;
+
+ @Inject
+ DomainManager _domainMgr;
+
@Override
public boolean start() {
if (isSAMLPluginEnabled()) {
@@ -327,19 +342,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
}
@Override
- public List<Class<?>> getAuthCommands() {
- List<Class<?>> cmdList = new ArrayList<Class<?>>();
- if (!isSAMLPluginEnabled()) {
- return cmdList;
- }
- cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
- cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
- cmdList.add(GetServiceProviderMetaDataCmd.class);
- cmdList.add(ListIdpsCmd.class);
- return cmdList;
- }
-
- @Override
public SAMLProviderMetadata getSPMetadata() {
return _spMetadata;
}
@@ -365,6 +367,63 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
return _idpMetadataMap.values();
}
+ @Override
+ public boolean isUserAuthorized(Long userId, String entityId) {
+ UserVO user = _userDao.getUser(userId);
+ if (user != null) {
+ if (user.getSource().equals(User.Source.SAML2) &&
+ user.getExternalEntity().equalsIgnoreCase(entityId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean authorizeUser(Long userId, String entityId, boolean enable) {
+ UserVO user = _userDao.getUser(userId);
+ if (user != null) {
+ if (enable) {
+ user.setExternalEntity(entityId);
+ user.setSource(User.Source.SAML2);
+ } else {
+ if (user.getSource().equals(User.Source.SAML2)) {
+ user.setSource(User.Source.SAML2DISABLED);
+ } else {
+ return false;
+ }
+ }
+ _userDao.update(user.getId(), user);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void saveToken(String authnId, String domainPath, String entity) {
+ Long domainId = null;
+ Domain domain = _domainMgr.findDomainByPath(domainPath);
+ if (domain != null) {
+ domainId = domain.getId();
+ }
+ SAMLTokenVO token = new SAMLTokenVO(authnId, domainId, entity);
+ if (_samlTokenDao.findByUuid(authnId) == null) {
+ _samlTokenDao.persist(token);
+ } else {
+ s_logger.warn("Duplicate SAML token for entity=" + entity + " token id=" + authnId + " domain=" + domainPath);
+ }
+ }
+
+ @Override
+ public SAMLTokenVO getToken(String authnId) {
+ return _samlTokenDao.findByUuid(authnId);
+ }
+
+ @Override
+ public void expireTokens() {
+ _samlTokenDao.expireTokens();
+ }
+
public Boolean isSAMLPluginEnabled() {
return SAMLIsPluginEnabled.value();
}
@@ -375,6 +434,29 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
}
@Override
+ public List<Class<?>> getAuthCommands() {
+ List<Class<?>> cmdList = new ArrayList<Class<?>>();
+ if (!isSAMLPluginEnabled()) {
+ return cmdList;
+ }
+ cmdList.add(SAML2LoginAPIAuthenticatorCmd.class);
+ cmdList.add(SAML2LogoutAPIAuthenticatorCmd.class);
+ cmdList.add(GetServiceProviderMetaDataCmd.class);
+ cmdList.add(ListIdpsCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public List<Class<?>> getCommands() {
+ List<Class<?>> cmdList = new ArrayList<Class<?>>();
+ if (!isSAMLPluginEnabled()) {
+ return cmdList;
+ }
+ cmdList.add(AuthorizeSAMLSSOCmd.class);
+ return cmdList;
+ }
+
+ @Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {
SAMLIsPluginEnabled, SAMLServiceProviderID,
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/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
index 92a17a9..5c8a390 100644
--- a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAML2UserAuthenticator.java
@@ -58,8 +58,8 @@ public class SAML2UserAuthenticator extends DefaultUserAuthenticator {
}
final UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
- if (userAccount == null) {
- s_logger.debug("Unable to find user with " + username + " in domain " + domainId);
+ if (userAccount == null || userAccount.getSource() != User.Source.SAML2) {
+ s_logger.debug("Unable to find user with " + username + " in domain " + domainId + ", or user source is not SAML2");
return new Pair<Boolean, ActionOnFailedAuthentication>(false, null);
} else {
User user = _userDao.getUser(userAccount.getId());
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
new file mode 100644
index 0000000..b045562
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDao.java
@@ -0,0 +1,23 @@
+// 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.utils.db.GenericDao;
+
+public interface SAMLTokenDao extends GenericDao<SAMLTokenVO, Long> {
+ public void expireTokens();
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
new file mode 100644
index 0000000..d6fb938
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenDaoImpl.java
@@ -0,0 +1,43 @@
+// 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.utils.db.DB;
+import com.cloud.utils.db.GenericDaoBase;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.ejb.Local;
+
+@DB
+@Component
+@Local(value = {SAMLTokenDao.class})
+public class SAMLTokenDaoImpl extends GenericDaoBase<SAMLTokenVO, Long> implements SAMLTokenDao {
+
+ public SAMLTokenDaoImpl() {
+ super();
+ }
+
+ @PostConstruct
+ protected void init() {
+ }
+
+ @Override
+ public void expireTokens() {
+ // TODO: implement delete all from token where timestamp is older than certain thresholds
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
----------------------------------------------------------------------
diff --git a/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
new file mode 100644
index 0000000..c8ac2f1
--- /dev/null
+++ b/plugins/user-authenticators/saml2/src/org/apache/cloudstack/saml/SAMLTokenVO.java
@@ -0,0 +1,97 @@
+// 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.utils.db.GenericDao;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+@Entity
+@Table(name = "saml_token")
+public class SAMLTokenVO implements Identity, InternalIdentity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "domain_id")
+ private Long domainId = null;
+
+ @Column(name = "entity")
+ private String entity = null;
+
+ @Column(name = GenericDao.CREATED_COLUMN)
+ private Date created;
+
+ public SAMLTokenVO() {
+ }
+
+ public SAMLTokenVO(String uuid, Long domainId, String entity) {
+ this.uuid = uuid;
+ this.domainId = domainId;
+ this.entity = entity;
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public Long getDomainId() {
+ return domainId;
+ }
+
+ public void setDomainId(long domainId) {
+ this.domainId = domainId;
+ }
+
+ public String getEntity() {
+ return entity;
+ }
+
+ public void setEntity(String entity) {
+ this.entity = entity;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/138da3c5/setup/db/db/schema-451to452.sql
----------------------------------------------------------------------
diff --git a/setup/db/db/schema-451to452.sql b/setup/db/db/schema-451to452.sql
index cd7f229..5c89008 100644
--- a/setup/db/db/schema-451to452.sql
+++ b/setup/db/db/schema-451to452.sql
@@ -20,3 +20,16 @@
--;
DELETE FROM `cloud`.`configuration` WHERE name like 'saml%';
+
+ALTER TABLE `cloud`.`user` ADD COLUMN `external_entity` text DEFAULT NULL COMMENT "reference to external federation entity";
+
+DROP TABLE IF EXISTS `cloud`.`saml_token`;
+CREATE TABLE `cloud`.`saml_token` (
+ `id` bigint unsigned NOT NULL AUTO_INCREMENT,
+ `uuid` varchar(255) UNIQUE NOT NULL COMMENT 'The Authn Unique Id',
+ `domain_id` bigint unsigned DEFAULT NULL,
+ `entity` text NOT NULL COMMENT 'Identity Provider Entity Id',
+ `created` DATETIME NOT NULL,
+ PRIMARY KEY (`id`),
+ CONSTRAINT `fk_saml_token__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
[2/2] git commit: updated refs/heads/saml-production-grade to 45cd42b
Posted by bh...@apache.org.
UI: in progress
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/45cd42b1
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/45cd42b1
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/45cd42b1
Branch: refs/heads/saml-production-grade
Commit: 45cd42b1af954bf6da67f3127dc6621e95eccb5b
Parents: 138da3c
Author: Rohit Yadav <ro...@shapeblue.com>
Authored: Wed Jun 10 14:48:12 2015 +0300
Committer: Rohit Yadav <ro...@shapeblue.com>
Committed: Thu Jun 11 18:26:01 2015 +0300
----------------------------------------------------------------------
ui/scripts/accountsWizard.js | 28 +++++++++++++++++++++++++++-
ui/scripts/docs.js | 8 ++++++++
ui/scripts/sharedFunctions.js | 1 +
ui/scripts/ui-custom/accountsWizard.js | 5 +++++
ui/scripts/ui-custom/login.js | 1 +
5 files changed, 42 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/45cd42b1/ui/scripts/accountsWizard.js
----------------------------------------------------------------------
diff --git a/ui/scripts/accountsWizard.js b/ui/scripts/accountsWizard.js
index 82e7eab..3747876 100644
--- a/ui/scripts/accountsWizard.js
+++ b/ui/scripts/accountsWizard.js
@@ -162,8 +162,34 @@
validation: {
required: false
}
+ },
+ samlEnable: {
+ label: 'label.saml.enable',
+ docID: 'helpSamlEnable',
+ isBoolean: true,
+ validation: {
+ required: false
+ }
+ },
+ samlEntity: {
+ label: 'label.saml.entity',
+ docID: 'helpSamlEntity',
+ validation: {
+ required: false
+ },
+ select: function(args) {
+ var items = [];
+ $(g_idpList).each(function() {
+ items.push({
+ id: this.id,
+ description: this.orgName
+ });
+ });
+ args.response.success({
+ data: items
+ });
+ }
}
-
},
action: function(args) {
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/45cd42b1/ui/scripts/docs.js
----------------------------------------------------------------------
diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js
index d38bcf7..ed94ccc 100755
--- a/ui/scripts/docs.js
+++ b/ui/scripts/docs.js
@@ -1247,6 +1247,14 @@ cloudStack.docs = {
desc: 'The group name from which you want to import LDAP users',
externalLink: ''
},
+ helpSamlEnable: {
+ desc: 'Enable SAML Single Sign On for the user(s)',
+ externalLink: ''
+ },
+ helpSamlEntity: {
+ desc: 'Choose the SAML Identity Provider Entity ID with which you want to enable the Single Sign On for the user(s)',
+ externalLink: ''
+ },
helpVpcOfferingName: {
desc: 'Any desired name for the VPC offering',
externalLink: ''
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/45cd42b1/ui/scripts/sharedFunctions.js
----------------------------------------------------------------------
diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js
index 1e1514b..75860dc 100644
--- a/ui/scripts/sharedFunctions.js
+++ b/ui/scripts/sharedFunctions.js
@@ -32,6 +32,7 @@ var g_regionsecondaryenabled = null;
var g_userPublicTemplateEnabled = "true";
var g_cloudstackversion = null;
var g_queryAsyncJobResultInterval = 3000;
+var g_idpList = null;
//keyboard keycode
var keycode_Enter = 13;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/45cd42b1/ui/scripts/ui-custom/accountsWizard.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/accountsWizard.js b/ui/scripts/ui-custom/accountsWizard.js
index 3259227..cfbe930 100644
--- a/ui/scripts/ui-custom/accountsWizard.js
+++ b/ui/scripts/ui-custom/accountsWizard.js
@@ -271,6 +271,11 @@
delete args.informationNotInLdap.ldapGroupName;
}
+ if (g_idpList == null) {
+ delete args.informationNotInLdap.samlEnable;
+ delete args.informationNotInLdap.samlEntity;
+ }
+
var informationNotInLdap = cloudStack.dialog.createForm({
context: context,
noDialog: true,
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/45cd42b1/ui/scripts/ui-custom/login.js
----------------------------------------------------------------------
diff --git a/ui/scripts/ui-custom/login.js b/ui/scripts/ui-custom/login.js
index 7f32e13..0c4c6fc 100644
--- a/ui/scripts/ui-custom/login.js
+++ b/ui/scripts/ui-custom/login.js
@@ -144,6 +144,7 @@
var idpList = data.listidpsresponse.idp.sort(function (a, b) {
return a.orgName.localeCompare(b.orgName);
});
+ g_idpList = idpList;
if (idpList.length > 1) {
$login.find('#saml-idps')
.append($('<option>', {