CLOUDSTACK-8191: SAML users should have their own accounts

(cherry picked from commit 876c78fe1ba6abe132131b3449b21fd09f2c14e1)
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2015-02-02 19:56:25 +05:30
parent 2f6691c6b9
commit 552f2ae60c
20 changed files with 92 additions and 45 deletions

View File

@ -758,6 +758,7 @@ label.local.storage=Local Storage
label.local=Local
label.login=Login
label.logout=Logout
label.saml.login=SAML Login
label.LUN.number=LUN \#
label.lun=LUN
label.make.project.owner=Make account project owner

View File

@ -572,6 +572,7 @@ label.local=Lokal
label.local.storage=Lokaler Speicher
label.login=Login
label.logout=Abmelden
label.saml.login=SAML Login
label.lun=LUN
label.LUN.number=LUN \#
label.management=Verwaltung

View File

@ -555,6 +555,7 @@ label.local=local
label.local.storage=Almacenamiento Local
label.login=Login
label.logout=Cerrar sesi\u00c3\u00b3n
label.saml.login=SAML Login
label.lun=LUN
label.LUN.number=LUN \#
label.manage=Administrar

View File

@ -878,6 +878,7 @@ label.local.storage.enabled=Stockage local activ\u00e9
label.local.storage=Stockage local
label.login=Connexion
label.logout=D\u00e9connexion
label.saml.login=SAML Connexion
label.lun=LUN
label.LUN.number=N\u00b0 LUN
label.lxc.traffic.label=Libell\u00e9 trafic LXC

View File

@ -755,6 +755,7 @@ label.local.storage=\u30ed\u30fc\u30ab\u30eb \u30b9\u30c8\u30ec\u30fc\u30b8
label.local=\u30ed\u30fc\u30ab\u30eb
label.login=\u30ed\u30b0\u30aa\u30f3
label.logout=\u30ed\u30b0\u30aa\u30d5
label.saml.login=SAML \u30ed\u30b0\u30aa\u30f3
label.LUN.number=LUN \u756a\u53f7
label.lun=LUN
label.make.project.owner=\u30a2\u30ab\u30a6\u30f3\u30c8\u306e\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u6240\u6709\u8005\u3078\u306e\u5909\u66f4
@ -2085,4 +2086,4 @@ label.timezone.colon=\u30bf\u30a4\u30e0\u30be\u30fc\u30f3\:
label.keep.colon=\u4fdd\u6301\u6570\:
label.every=\u6bce\u9031
label.day=\u6bce\u6708
label.of.month=\u65e5
label.of.month=\u65e5

View File

@ -651,6 +651,7 @@ label.local.storage=\ub85c\uceec \uc2a4\ud1a0\ub9ac\uc9c0
label.local=\ub85c\uceec
label.login=\ub85c\uadf8\uc778
label.logout=\ub85c\uadf8\uc544\uc6c3
label.saml.login=SAML \ub85c\uadf8\uc778
label.lun=LUN
label.LUN.number=LUN \ubc88\ud638
label.make.project.owner=\uacc4\uc815 \uc815\ubcf4 \ud504\ub85c\uc81d\ud2b8 \uc18c\uc720\uc790

View File

@ -826,6 +826,7 @@ label.local.storage.enabled=Lokale opslag ingeschakeld
label.local.storage=Lokale Opslag
label.login=Login
label.logout=Log uit
label.saml.login=SAML Login
label.lun=LUN
label.LUN.number=LUN \#
label.lxc.traffic.label=LXC verkeerslabel

View File

@ -292,6 +292,7 @@ label.local.storage.enabled=Pami\u0119\u0107 lokalna w\u0142\u0105czona
label.local.storage=Pami\u0119\u0107 lokalna
label.login=Zaloguj
label.logout=Wyloguj
label.saml.login=SAML Zaloguj
label.lun=LUN
label.LUN.number=LUN \#
label.max.guest.limit=Maksymalna liczba go\u015bci

View File

@ -728,6 +728,7 @@ label.local.storage.enabled=Storage local habilitada
label.local.storage=Storage Local
label.login=Entrar
label.logout=Sair
label.saml.login=SAML Entrar
label.lun=LUN
label.LUN.number=LUN \#
label.make.project.owner=Criar propriet\u00e1rio de conta de projeto

View File

@ -689,6 +689,7 @@ label.local.storage=\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0435 \u044
label.local=\u041b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439
label.login=\u0412\u0445\u043e\u0434
label.logout=\u0412\u044b\u0445\u043e\u0434
label.saml.login=SAML \u0412\u0445\u043e\u0434
label.lun=LUN
label.LUN.number=LUN \#
label.make.project.owner=\u0421\u0434\u0435\u043b\u0430\u0442\u044c \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430

View File

@ -755,6 +755,7 @@ label.local.storage=\u672c\u5730\u5b58\u50a8
label.local=\u672c\u5730
label.login=\u767b\u5f55
label.logout=\u6ce8\u9500
label.saml.login=SAML \u767b\u5f55
label.LUN.number=LUN \u53f7
label.lun=LUN
label.make.project.owner=\u8bbe\u4e3a\u5e10\u6237\u9879\u76ee\u6240\u6709\u8005
@ -2085,4 +2086,4 @@ label.timezone.colon=\u65f6\u533a\:
label.keep.colon=\u4fdd\u7559\u6570\u91cf\:
label.every=\u6bcf
label.day=\u6bcf\u6708
label.of.month=\u65e5
label.of.month=\u65e5

View File

@ -23,7 +23,8 @@ import com.cloud.domain.Domain;
import com.cloud.exception.CloudAuthenticationException;
import com.cloud.user.Account;
import com.cloud.user.DomainManager;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.db.EntityManager;
import org.apache.cloudstack.api.APICommand;
@ -73,6 +74,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@APICommand(name = "samlSso", description = "SP initiated SAML Single Sign On", requestHasSensitiveInfo = true, responseObject = LoginCmdResponse.class, entityType = {})
public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthenticator {
@ -93,6 +95,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
ConfigurationDao _configDao;
@Inject
DomainManager _domainMgr;
@Inject
private UserAccountDao _userAccountDao;
SAML2AuthManager _samlAuthManager;
@ -201,11 +205,9 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
}
}
String uniqueUserId = null;
String accountName = _configDao.getValue(Config.SAMLUserAccountName.key());
String domainString = _configDao.getValue(Config.SAMLUserDomain.key());
Long domainId = -1L;
Long domainId = null;
Domain domain = _domainMgr.getDomain(domainString);
if (domain != null) {
domainId = domain.getId();
@ -215,16 +217,17 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
} catch (NumberFormatException ignore) {
}
}
if (domainId == -1L) {
s_logger.error("The default domain ID for SAML users is not set correct, it should be a UUID");
if (domainId == null) {
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 username = null;
String password = SAMLUtils.generateSecureRandomId(); // Random password
String firstName = "";
String lastName = "";
String timeZone = "";
String timeZone = "GMT";
String email = "";
short accountType = 0; // User account
Assertion assertion = processedSAMLResponse.getAssertions().get(0);
NameID nameId = assertion.getSubject().getNameID();
@ -234,7 +237,6 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
if (nameId.getFormat().equals(NameIDType.PERSISTENT) || nameId.getFormat().equals(NameIDType.EMAIL)) {
username = nameId.getValue();
uniqueUserId = SAMLUtils.createSAMLId(username);
if (nameId.getFormat().equals(NameIDType.EMAIL)) {
email = username;
}
@ -250,9 +252,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
for (Attribute attribute: attributeStatement.getAttributes()) {
String attributeName = attribute.getName();
String attributeValue = attribute.getAttributeValues().get(0).getDOM().getTextContent();
if (attributeName.equalsIgnoreCase("uid") && uniqueUserId == null) {
if (attributeName.equalsIgnoreCase("uid") && username == null) {
username = attributeValue;
uniqueUserId = SAMLUtils.createSAMLId(username);
} else if (attributeName.equalsIgnoreCase("givenName")) {
firstName = attributeValue;
} else if (attributeName.equalsIgnoreCase(("sn"))) {
@ -264,17 +265,22 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent
}
}
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 (username == null && email != null) {
username = email;
}
final String uniqueUserId = SAMLUtils.createSAMLId(username);
UserAccount userAccount = _userAccountDao.getUserAccount(username, domainId);
if (userAccount == null && uniqueUserId != null && username != null) {
CallContext.current().setEventDetails("SAML Account/User with UserName: " + username + ", FirstName :" + password + ", LastName: " + lastName);
_accountService.createUserAccount(username, password, firstName, lastName, email, timeZone,
username, (short) accountType, domainId, null, null, UUID.randomUUID().toString(), uniqueUserId);
}
if (user != null) {
if (userAccount != null) {
try {
if (_apiServer.verifyUser(user.getId())) {
LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, username, user.getPassword(), domainId, null, remoteAddress, params);
if (_apiServer.verifyUser(userAccount.getId())) {
LoginCmdResponse loginResponse = (LoginCmdResponse) _apiServer.loginUser(session, username, userAccount.getPassword(), domainId, 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)));

View File

@ -22,10 +22,9 @@ package org.apache.cloudstack.api.command;
import com.cloud.domain.Domain;
import com.cloud.user.AccountService;
import com.cloud.user.DomainManager;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.user.UserAccountVO;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.db.EntityManager;
import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.ServerApiException;
@ -79,15 +78,15 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
@Mock
ConfigurationDao configDao;
@Mock
EntityManager entityMgr;
@Mock
DomainManager domainMgr;
@Mock
AccountService accountService;
@Mock
UserAccountDao userAccountDao;
@Mock
Domain domain;
@ -139,10 +138,6 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
accountServiceField.setAccessible(true);
accountServiceField.set(cmd, accountService);
Field entityMgrField = SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_entityMgr");
entityMgrField.setAccessible(true);
entityMgrField.set(cmd, entityMgr);
Field domainMgrField = SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_domainMgr");
domainMgrField.setAccessible(true);
domainMgrField.set(cmd, domainMgr);
@ -151,6 +146,10 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
configDaoField.setAccessible(true);
configDaoField.set(cmd, configDao);
Field userAccountDaoField = SAML2LoginAPIAuthenticatorCmd.class.getDeclaredField("_userAccountDao");
userAccountDaoField.setAccessible(true);
userAccountDaoField.set(cmd, userAccountDao);
String spId = "someSPID";
String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
@ -164,9 +163,10 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
Mockito.when(domain.getId()).thenReturn(1L);
Mockito.when(domainMgr.getDomain(Mockito.anyString())).thenReturn(domain);
UserVO user = new UserVO();
user.setUuid(SAMLUtils.createSAMLId("someUID"));
Mockito.when(entityMgr.findByUuid(Mockito.eq(User.class), Mockito.anyString())).thenReturn((User) user);
UserAccountVO user = new UserAccountVO();
user.setUsername(SAMLUtils.createSAMLId("someUID"));
user.setId(1000L);
Mockito.when(userAccountDao.getUserAccount(Mockito.anyString(), Mockito.anyLong())).thenReturn(user);
Mockito.when(apiServer.verifyUser(Mockito.anyLong())).thenReturn(false);
Map<String, Object[]> params = new HashMap<String, Object[]>();
@ -184,7 +184,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
}
Mockito.verify(configDao, Mockito.atLeastOnce()).getValue(Mockito.anyString());
Mockito.verify(domainMgr, Mockito.times(1)).getDomain(Mockito.anyString());
Mockito.verify(entityMgr, Mockito.times(1)).findByUuid(Mockito.eq(User.class), Mockito.anyString());
Mockito.verify(userAccountDao, Mockito.times(1)).getUserAccount(Mockito.anyString(), Mockito.anyLong());
Mockito.verify(apiServer, Mockito.times(1)).verifyUser(Mockito.anyLong());
}

View File

@ -1379,14 +1379,6 @@ public enum Config {
"false",
"Set it to true to enable SAML SSO plugin",
null),
SAMLUserAccountName(
"Advanced",
ManagementServer.class,
String.class,
"saml2.default.accountname",
"admin",
"The name of the default account to use when creating users from SAML SSO",
null),
SAMLUserDomain(
"Advanced",
ManagementServer.class,

View File

@ -1017,7 +1017,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
throw new InvalidParameterValueException("The user " + userName + " already exists in domain " + domainId);
}
if (networkDomain != null) {
if (networkDomain != null && networkDomain.length() > 0) {
if (!NetUtils.verifyDomainName(networkDomain)) {
throw new InvalidParameterValueException(
"Invalid network domain. Total length shouldn't exceed 190 chars. Each domain label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', "

View File

@ -440,7 +440,7 @@ body.login {
background: transparent url(../images/sprites.png) -563px -747px;
cursor: pointer;
border: none;
margin: 7px 238px 0 -1px;
margin: 7px 120px 0 -1px;
text-align: center;
width: 69px;
height: 25px;
@ -456,6 +456,27 @@ body.login {
text-shadow: 0px 1px 2px #000000;
}
.login .fields input[type=samlsubmit] {
background: transparent url(../images/sprites.png) -563px -747px;
cursor: pointer;
border: none;
margin: 7px 120px 0 -1px;
text-align: center;
width: 60px;
height: 15px;
display: block;
color: #FFFFFF;
font-weight: bold;
float: left;
text-indent: -1px;
/*+text-shadow:0px 1px 2px #000000;*/
-moz-text-shadow: 0px 1px 2px #000000;
-webkit-text-shadow: 0px 1px 2px #000000;
-o-text-shadow: 0px 1px 2px #000000;
text-shadow: 0px 1px 2px #000000;
font-size: 10px;
}
.login .fields input[type=submit]:hover {
background-position: -563px -772px;
}
@ -13035,4 +13056,3 @@ div.gpugroups div.list-view {
background: transparent url("../images/icons.png") no-repeat -626px -209px;
padding: 0 0 3px 18px;
}

View File

@ -762,6 +762,7 @@ dictionary = {
'label.local.storage': '<fmt:message key="label.local.storage" />',
'label.login': '<fmt:message key="label.login" />',
'label.logout': '<fmt:message key="label.logout" />',
'label.saml.login': '<fmt:message key="label.saml.login" />',
'label.lun': '<fmt:message key="label.lun" />',
'label.LUN.number': '<fmt:message key="label.LUN.number" />',
'label.make.project.owner': '<fmt:message key="label.make.project.owner" />',

View File

@ -67,6 +67,7 @@
</div>
<!-- Submit (login) -->
<input type="submit" value="<fmt:message key="label.login"/>" />
<input type="samlsubmit" value="<fmt:message key="label.saml.login"/>" />
<!-- Select language -->
<div class="select-language">
<select name="language">

View File

@ -346,6 +346,16 @@
});
},
samlLoginAction: function(args) {
$.cookie('sessionKey', null);
$.cookie('username', null);
$.cookie('account', null);
$.cookie('domainid', null);
$.cookie('role', null);
$.cookie('timezone', null);
window.location.href = createURL('samlSso');
},
// Show cloudStack main UI widget
complete: function(args) {
var context = {

View File

@ -120,6 +120,12 @@
return false;
});
// SAML Login action
$login.find('input[type=samlsubmit]').click(function() {
args.samlLoginAction({
});
});
// Select language
var $languageSelect = $login.find('select[name=language]');
$languageSelect.change(function() {