Skip to content

Commit bc42cd1

Browse files
author
Daan Hoogland
committed
make sure pre saml login is enabled after saml is disabled
1 parent 5e5ae0c commit bc42cd1

2 files changed

Lines changed: 89 additions & 1 deletion

File tree

plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
import org.apache.cloudstack.framework.config.Configurable;
5555
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
5656
import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
57+
import org.apache.cloudstack.resourcedetail.UserDetailVO;
58+
import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao;
5759
import org.apache.cloudstack.utils.security.CertUtils;
5860
import org.apache.commons.codec.binary.Base64;
5961
import org.apache.commons.httpclient.HttpClient;
@@ -92,6 +94,10 @@
9294
@Component
9395
public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable {
9496

97+
/** Remembers the user's Source (e.g. LDAP) from before SAML was authorized, so disabling
98+
* SAML can fall back to it instead of always defaulting to {@link User.Source#UNKNOWN}. */
99+
private static final String PRE_SAML_SOURCE_DETAIL_KEY = "PreSamlSource";
100+
95101
private SAMLProviderMetadata _spMetadata = new SAMLProviderMetadata();
96102
private Map<String, SAMLProviderMetadata> _idpMetadataMap = new HashMap<String, SAMLProviderMetadata>();
97103

@@ -115,6 +121,9 @@ public String getSAMLIdentityProviderMetadataURL(){
115121
@Inject
116122
private UserDao _userDao;
117123

124+
@Inject
125+
private UserDetailsDao userDetailsDao;
126+
118127
@Inject
119128
DomainManager _domainMgr;
120129

@@ -448,13 +457,16 @@ public boolean authorizeUser(Long userId, String entityId, boolean enable) {
448457
UserVO user = _userDao.getUser(userId);
449458
if (user != null) {
450459
if (enable) {
460+
if (user.getSource() != null && !User.Source.SAML2.equals(user.getSource()) && !User.Source.SAML2DISABLED.equals(user.getSource())) {
461+
userDetailsDao.addDetail(user.getId(), PRE_SAML_SOURCE_DETAIL_KEY, user.getSource().toString(), false);
462+
}
451463
user.setExternalEntity(entityId);
452464
user.setSource(User.Source.SAML2);
453465
} else {
454466
boolean enableLoginAfterSAMLDisable = SAML2AuthManager.EnableLoginAfterSAMLDisable.value();
455467
if (user.getSource().equals(User.Source.SAML2)) {
456468
if(enableLoginAfterSAMLDisable) {
457-
user.setSource(User.Source.UNKNOWN);
469+
user.setSource(getPreSamlSource(user.getId()));
458470
} else {
459471
user.setSource(User.Source.SAML2DISABLED);
460472
}
@@ -468,6 +480,22 @@ public boolean authorizeUser(Long userId, String entityId, boolean enable) {
468480
return false;
469481
}
470482

483+
/**
484+
* The Source (e.g. LDAP) the user had before SAML was authorized for them, so disabling
485+
* SAML can restore it instead of always falling back to {@link User.Source#UNKNOWN}.
486+
*/
487+
private User.Source getPreSamlSource(long userId) {
488+
UserDetailVO preSamlSource = userDetailsDao.findDetail(userId, PRE_SAML_SOURCE_DETAIL_KEY);
489+
if (preSamlSource != null) {
490+
try {
491+
return User.Source.valueOf(preSamlSource.getValue());
492+
} catch (IllegalArgumentException e) {
493+
logger.warn("Unrecognized pre-SAML source '{}' stored for user {}; falling back to UNKNOWN", preSamlSource.getValue(), userId);
494+
}
495+
}
496+
return User.Source.UNKNOWN;
497+
}
498+
471499
@Override
472500
public void saveToken(String authnId, String domainPath, String entity) {
473501
Long domainId = null;

plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/SAML2AuthManagerImplTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
package org.apache.cloudstack;
2121

2222
import java.lang.reflect.Field;
23+
import java.lang.reflect.Method;
2324

2425
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
26+
import org.apache.cloudstack.resourcedetail.UserDetailVO;
27+
import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao;
2528
import org.apache.cloudstack.saml.SAML2AuthManagerImpl;
2629
import org.apache.cloudstack.saml.SAMLTokenDao;
2730
import org.apache.cloudstack.saml.SAMLTokenVO;
@@ -50,6 +53,9 @@ public class SAML2AuthManagerImplTest extends TestCase {
5053
@Mock
5154
private UserDao userDao;
5255

56+
@Mock
57+
private UserDetailsDao userDetailsDao;
58+
5359
@Mock
5460
DomainManager domainMgr;
5561

@@ -72,6 +78,10 @@ public void setUp() throws NoSuchFieldException, IllegalAccessException {
7278
userDaoField.setAccessible(true);
7379
userDaoField.set(saml2AuthManager, userDao);
7480

81+
Field userDetailsDaoField = SAML2AuthManagerImpl.class.getDeclaredField("userDetailsDao");
82+
userDetailsDaoField.setAccessible(true);
83+
userDetailsDaoField.set(saml2AuthManager, userDetailsDao);
84+
7585
Field domainMgrField = SAML2AuthManagerImpl.class.getDeclaredField("_domainMgr");
7686
domainMgrField.setAccessible(true);
7787
domainMgrField.set(saml2AuthManager, domainMgr);
@@ -117,7 +127,57 @@ public void testAuthorizeUser() {
117127
Mockito.verify(userDao, Mockito.atLeastOnce()).update(Mockito.anyLong(), Mockito.any(user.getClass()));
118128
}
119129

130+
@Test
131+
public void testAuthorizeUserStoresPreSamlSourceOnEnable() {
132+
UserVO user = new UserVO(200L);
133+
user.setUsername("someuser");
134+
user.setSource(User.Source.LDAP);
135+
Mockito.when(userDao.getUser(Mockito.anyLong())).thenReturn(user);
136+
137+
saml2AuthManager.authorizeUser(200L, "someID", true);
138+
139+
Mockito.verify(userDetailsDao).addDetail(200L, "PreSamlSource", "LDAP", false);
140+
assertEquals(User.Source.SAML2, user.getSource());
141+
}
142+
143+
@Test
144+
public void testAuthorizeUserDoesNotRestorePreSamlSourceWhenAlreadyAuthorized() {
145+
UserVO user = new UserVO(200L);
146+
user.setUsername("someuser");
147+
user.setSource(User.Source.SAML2);
148+
Mockito.when(userDao.getUser(Mockito.anyLong())).thenReturn(user);
149+
150+
saml2AuthManager.authorizeUser(200L, "someID", true);
151+
152+
Mockito.verify(userDetailsDao, Mockito.never()).addDetail(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyBoolean());
153+
}
154+
155+
@Test
156+
public void testGetPreSamlSourceRestoresStoredSource() throws Exception {
157+
Mockito.when(userDetailsDao.findDetail(200L, "PreSamlSource")).thenReturn(new UserDetailVO(200L, "PreSamlSource", "LDAP"));
158+
159+
assertEquals(User.Source.LDAP, invokeGetPreSamlSource(200L));
160+
}
161+
162+
@Test
163+
public void testGetPreSamlSourceDefaultsToUnknownWhenNothingStored() throws Exception {
164+
Mockito.when(userDetailsDao.findDetail(200L, "PreSamlSource")).thenReturn(null);
165+
166+
assertEquals(User.Source.UNKNOWN, invokeGetPreSamlSource(200L));
167+
}
168+
169+
@Test
170+
public void testGetPreSamlSourceDefaultsToUnknownOnGarbageValue() throws Exception {
171+
Mockito.when(userDetailsDao.findDetail(200L, "PreSamlSource")).thenReturn(new UserDetailVO(200L, "PreSamlSource", "not-a-real-source"));
172+
173+
assertEquals(User.Source.UNKNOWN, invokeGetPreSamlSource(200L));
174+
}
120175

176+
private User.Source invokeGetPreSamlSource(long userId) throws Exception {
177+
Method method = SAML2AuthManagerImpl.class.getDeclaredMethod("getPreSamlSource", long.class);
178+
method.setAccessible(true);
179+
return (User.Source) method.invoke(saml2AuthManager, userId);
180+
}
121181

122182
@Test
123183
public void testSaveToken() {

0 commit comments

Comments
 (0)