/*
* 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.testsuite.federation.sync;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.user.SynchronizationResult;
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.timer.TimerProvider;
import java.util.concurrent.TimeUnit;
/**
* Test with Dummy providers
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SyncFederationTest {
private static final Logger log = Logger.getLogger(SyncFederationTest.class);
private static UserStorageProviderModel dummyModel = null;
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
// Other tests may left Time offset uncleared, which could cause issues
Time.setOffset(0);
}
});
/**
* Test that period sync is triggered when creating a synchronized User Storage Provider
*
*/
@Test
public void test01PeriodicSyncOnCreate() {
KeycloakSession session = keycloakRule.startSession();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
int full = dummyFedFactory.getFullSyncCounter();
int changed = dummyFedFactory.getChangedSyncCounter();
keycloakRule.stopSession(session, false);
// Enable timer for SyncDummyUserFederationProvider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
UserStorageProviderModel model = new UserStorageProviderModel();
model.setProviderId(DummyUserFederationProviderFactory.PROVIDER_NAME);
model.setPriority(1);
model.setName("test-sync-dummy");
model.setFullSyncPeriod(-1);
model.setChangedSyncPeriod(1);
model.setLastSync(0);
dummyModel = new UserStorageProviderModel(appRealm.addComponentModel(model));
}
});
session = keycloakRule.startSession();
try {
// Assert that after some period was DummyUserFederationProvider triggered
UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
sleep(1800);
// Cancel timer
RealmModel appRealm = session.realms().getRealmByName("test");
usersSyncManager.notifyToRefreshPeriodicSync(session, appRealm, dummyModel, true);
log.infof("Notified sync manager about cancel periodic sync");
// This sync is here just to ensure that we have lock (doublecheck that periodic sync, which was possibly triggered before canceling timer is finished too)
while (true) {
SynchronizationResult result = usersSyncManager.syncChangedUsers(session.getKeycloakSessionFactory(), appRealm.getId(), dummyModel);
if (result.isIgnored()) {
log.infof("Still waiting for lock before periodic sync is finished", result.toString());
sleep(1000);
} else {
break;
}
}
// Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked at least 2 times (once periodically and once for us)
int newChanged = dummyFedFactory.getChangedSyncCounter();
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
Assert.assertTrue("Assertion failed. newChanged=" + newChanged + ", changed=" + changed, newChanged > (changed + 1));
// Assert that dummy provider won't be invoked anymore
sleep(1800);
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
int newestChanged = dummyFedFactory.getChangedSyncCounter();
Assert.assertEquals("Assertion failed. newChanged=" + newChanged + ", newestChanged=" + newestChanged, newChanged, newestChanged);
} finally {
keycloakRule.stopSession(session, true);
}
// remove dummyProvider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.removeComponent(dummyModel);
}
});
}
/**
* Test that period sync is triggered when updating a synchronized User Storage Provider to have a non-negative sync period
*
*/
@Test
public void test02PeriodicSyncOnUpdate() {
KeycloakSession session = keycloakRule.startSession();
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory) sessionFactory.getProviderFactory(UserStorageProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
int full = dummyFedFactory.getFullSyncCounter();
int changed = dummyFedFactory.getChangedSyncCounter();
keycloakRule.stopSession(session, false);
// Enable timer for SyncDummyUserFederationProvider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
UserStorageProviderModel model = new UserStorageProviderModel();
model.setProviderId(DummyUserFederationProviderFactory.PROVIDER_NAME);
model.setPriority(1);
model.setName("test-sync-dummy");
model.setFullSyncPeriod(-1);
model.setChangedSyncPeriod(-1);
model.setLastSync(0);
dummyModel = new UserStorageProviderModel(appRealm.addComponentModel(model));
}
});
session = keycloakRule.startSession();
try {
// Assert that after some period was DummyUserFederationProvider triggered
UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
// Assert that dummy provider won't be invoked anymore
sleep(1800);
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
int newestChanged = dummyFedFactory.getChangedSyncCounter();
Assert.assertEquals("Assertion failed. newChanged=" + changed + ", newestChanged=" + newestChanged, changed, newestChanged);
} finally {
keycloakRule.stopSession(session, true);
}
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
dummyModel.setChangedSyncPeriod(1);
appRealm.updateComponent(dummyModel);
}
});
session = keycloakRule.startSession();
try {
// Assert that after some period was DummyUserFederationProvider triggered
UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
sleep(1800);
// Cancel timer
RealmModel appRealm = session.realms().getRealmByName("test");
usersSyncManager.notifyToRefreshPeriodicSync(session, appRealm, dummyModel, true);
log.infof("Notified sync manager about cancel periodic sync");
// This sync is here just to ensure that we have lock (doublecheck that periodic sync, which was possibly triggered before canceling timer is finished too)
while (true) {
SynchronizationResult result = usersSyncManager.syncChangedUsers(session.getKeycloakSessionFactory(), appRealm.getId(), dummyModel);
if (result.isIgnored()) {
log.infof("Still waiting for lock before periodic sync is finished", result.toString());
sleep(1000);
} else {
break;
}
}
// Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked at least 2 times (once periodically and once for us)
int newChanged = dummyFedFactory.getChangedSyncCounter();
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
log.info("Asserting. newChanged=" + newChanged + " > changed=" + changed);
Assert.assertTrue("Assertion failed. newChanged=" + newChanged + ", changed=" + changed, newChanged > (changed + 1));
// Assert that dummy provider won't be invoked anymore
sleep(1800);
Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
int newestChanged = dummyFedFactory.getChangedSyncCounter();
Assert.assertEquals("Assertion failed. newChanged=" + newChanged + ", newestChanged=" + newestChanged, newChanged, newestChanged);
} finally {
keycloakRule.stopSession(session, true);
}
// remove dummyProvider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.removeComponent(dummyModel);
}
});
}
@Test
public void test03ConcurrentSync() throws Exception {
SyncDummyUserFederationProviderFactory.restartLatches();
// Enable timer for SyncDummyUserFederationProvider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
UserStorageProviderModel model = new UserStorageProviderModel();
model.setProviderId(SyncDummyUserFederationProviderFactory.SYNC_PROVIDER_ID);
model.setPriority(1);
model.setName("test-sync-dummy");
model.setFullSyncPeriod(-1);
model.setChangedSyncPeriod(1);
model.setLastSync(0);
model.getConfig().putSingle(SyncDummyUserFederationProviderFactory.WAIT_TIME, "2000");
dummyModel = new UserStorageProviderModel(appRealm.addComponentModel(model));
}
});
KeycloakSession session = keycloakRule.startSession();
try {
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
// bootstrap periodic sync
UserStorageSyncManager usersSyncManager = new UserStorageSyncManager();
usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
// Wait and then trigger sync manually. Assert it will be ignored
sleep(1800);
RealmModel realm = session.realms().getRealm("test");
SynchronizationResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, realm.getId(), dummyModel);
Assert.assertTrue(syncResult.isIgnored());
// Cancel timer
usersSyncManager.notifyToRefreshPeriodicSync(session, realm, dummyModel, true);
// Signal to factory to finish waiting
SyncDummyUserFederationProviderFactory.latch1.countDown();
} finally {
keycloakRule.stopSession(session, true);
}
SyncDummyUserFederationProviderFactory.latch2.await(20000, TimeUnit.MILLISECONDS);
// remove provider
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.removeComponent(dummyModel);
}
});
}
private void sleep(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException ie) {
throw new RuntimeException(ie);
}
}
}