KEYCLOAK-15139 Backwards compatibility for LDAP Read-only mode with IMPORT_USERS enabled
This commit is contained in:
parent
f486e97c18
commit
bd48d7914d
3 changed files with 219 additions and 0 deletions
|
@ -175,7 +175,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
|
|||
|
||||
switch (editMode) {
|
||||
case READ_ONLY:
|
||||
if (model.isImportEnabled()) {
|
||||
proxied = new ReadonlyLDAPUserModelDelegate(local);
|
||||
} else {
|
||||
proxied = new ReadOnlyUserModelDelegate(local);
|
||||
}
|
||||
break;
|
||||
case WRITABLE:
|
||||
case UNSYNCED:
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.storage.ldap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
|
||||
/**
|
||||
* Will be good to get rid of this class and use ReadOnlyUserModelDelegate, but it can't be done now due the backwards compatibility.
|
||||
* See KEYCLOAK-15139 as an example
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
|
||||
|
||||
public ReadonlyLDAPUserModelDelegate(UserModel delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String first) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, List<String> values) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
throw new ReadOnlyException("Federated storage is not writable");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed 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.keycloak.testsuite.federation.ldap;
|
||||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
|
||||
import org.keycloak.testsuite.util.LDAPRule;
|
||||
import org.keycloak.testsuite.util.LDAPTestUtils;
|
||||
import org.openqa.selenium.By;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Test for more advanced scenarios related to LDAP read-only mode
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class LDAPReadOnlyTest extends AbstractLDAPTest {
|
||||
|
||||
@ClassRule
|
||||
public static LDAPRule ldapRule = new LDAPRule();
|
||||
|
||||
@Override
|
||||
protected LDAPRule getLDAPRule() {
|
||||
return ldapRule;
|
||||
}
|
||||
|
||||
@Page
|
||||
protected LoginConfigTotpPage totpPage;
|
||||
|
||||
private TimeBasedOTP totp = new TimeBasedOTP();
|
||||
|
||||
@Override
|
||||
protected void afterImportTestRealm() {
|
||||
testingClient.server().run(session -> {
|
||||
LDAPTestContext ctx = LDAPTestContext.init(session);
|
||||
RealmModel appRealm = ctx.getRealm();
|
||||
|
||||
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
|
||||
|
||||
// Delete all LDAP users and add some new for testing
|
||||
LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
|
||||
|
||||
LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
|
||||
LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
|
||||
|
||||
LDAPObject existing = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
|
||||
|
||||
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
|
||||
|
||||
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
|
||||
ldapFedProvider.getModel().put(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString());
|
||||
appRealm.updateComponent(ldapFedProvider.getModel());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// KEYCLOAK-15139
|
||||
@Test
|
||||
public void testReadOnlyWithTOTPEnabled() {
|
||||
// Set TOTP required
|
||||
setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
|
||||
// Authenticate as the LDAP user and assert it works
|
||||
loginPage.open();
|
||||
loginPage.login("johnkeycloak", "Password1");
|
||||
|
||||
assertTrue(totpPage.isCurrent());
|
||||
assertFalse(totpPage.isCancelDisplayed());
|
||||
|
||||
// KEYCLOAK-11753 - Verify OTP label element present on "Configure OTP" required action form
|
||||
driver.findElement(By.id("userLabel"));
|
||||
|
||||
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
|
||||
|
||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
// Revert TOTP
|
||||
setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement.CONDITIONAL);
|
||||
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "johnkeycloak");
|
||||
String totpCredentialId = user.credentials().stream()
|
||||
.filter(credentialRep -> credentialRep.getType().equals(OTPCredentialModel.TYPE))
|
||||
.findFirst().get().getId();
|
||||
user.removeCredential(totpCredentialId);
|
||||
}
|
||||
|
||||
private void setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement requirement) {
|
||||
adminClient.realm("test").flows().getExecutions("browser").
|
||||
stream().filter(execution -> execution.getDisplayName().equals("Browser - Conditional OTP"))
|
||||
.forEach(execution ->
|
||||
{execution.setRequirement(requirement.name());
|
||||
adminClient.realm("test").flows().updateExecutions("browser", execution);});
|
||||
}
|
||||
|
||||
|
||||
protected void assertFederatedUserLink(UserRepresentation user) {
|
||||
Assert.assertTrue(StorageId.isLocalStorage(user.getId()));
|
||||
Assert.assertNotNull(user.getFederationLink());
|
||||
Assert.assertEquals(user.getFederationLink(), ldapModelId);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue