use of org.keycloak.testsuite.arquillian.annotation.InitialDcState in project keycloak by keycloak.
the class ActionTokenCrossDCTest method sendResetPasswordEmailAfterNewNodeAdded.
@Test
@InitialDcState(authServers = ServerSetup.FIRST_NODE_IN_FIRST_DC)
public void sendResetPasswordEmailAfterNewNodeAdded() throws IOException, MessagingException {
log.debug("--DC: START sendResetPasswordEmailAfterNewNodeAdded");
disableDcOnLoadBalancer(DC.SECOND);
UserRepresentation userRep = new UserRepresentation();
userRep.setEnabled(true);
userRep.setUsername("user1");
userRep.setEmail("user1@test.com");
String id = createUser(userRep);
UserResource user = realm.users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String link = MailUtils.getPasswordResetEmailLink(message);
driver.navigate().to(link);
proceedPage.assertCurrent();
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
passwordUpdatePage.changePassword("new-pass", "new-pass");
assertEquals("Your account has been updated.", PageUtils.getPageTitle(driver));
disableDcOnLoadBalancer(DC.FIRST);
CrossDCTestEnricher.startAuthServerBackendNode(DC.SECOND, 1);
CrossDCTestEnricher.stopAuthServerBackendNode(DC.FIRST, 0);
enableLoadBalancerNode(DC.SECOND, 1);
Retry.execute(() -> {
driver.navigate().to(link);
errorPage.assertCurrent();
}, 3, 400);
log.debug("--DC: END sendResetPasswordEmailAfterNewNodeAdded");
}
use of org.keycloak.testsuite.arquillian.annotation.InitialDcState in project keycloak by keycloak.
the class CrossDCTestEnricher method beforeTest.
public void beforeTest(@Observes(precedence = -2) Before event) {
if (!suiteContext.isAuthServerCrossDc())
return;
// if annotation is present on method
InitialDcState annotation = event.getTestMethod().getAnnotation(InitialDcState.class);
// annotation not present on method, taking it from class
if (annotation == null) {
Class<?> annotatedClass = getNearestSuperclassWithAnnotation(event.getTestClass().getJavaClass(), InitialDcState.class);
annotation = annotatedClass == null ? null : annotatedClass.getAnnotation(InitialDcState.class);
}
if (annotation == null) {
log.debug("No environment preparation requested, not changing auth/cache server run status.");
// Test does not specify its environment, so it's on its own
return;
}
ServerSetup cacheServers = annotation.cacheServers();
ServerSetup authServers = annotation.authServers();
// Stop auth servers that otherwise could be hang connecting to a cache server stopped next
switch(authServers) {
case ALL_NODES_IN_EVERY_DC:
break;
case FIRST_NODE_IN_EVERY_DC:
DC.validDcsStream().forEach((DC dc) -> stopAuthServerBackendNode(dc, 1));
break;
case FIRST_NODE_IN_FIRST_DC:
stopAuthServerBackendNode(DC.FIRST, 1);
forAllBackendNodesInDc(DC.SECOND, CrossDCTestEnricher::stopAuthServerBackendNode);
break;
case ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC:
stopAuthServerBackendNode(DC.SECOND, 1);
break;
case ALL_NODES_IN_FIRST_DC_NO_NODES_IN_SECOND_DC:
forAllBackendNodesInDc(DC.SECOND, CrossDCTestEnricher::stopAuthServerBackendNode);
break;
}
switch(cacheServers) {
case ALL_NODES_IN_EVERY_DC:
// the same as ALL_NODES_IN_EVERY_DC as there is only one cache server per DC
case FIRST_NODE_IN_EVERY_DC:
case ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC:
DC.validDcsStream().forEach(CrossDCTestEnricher::startCacheServer);
break;
case FIRST_NODE_IN_FIRST_DC:
case ALL_NODES_IN_FIRST_DC_NO_NODES_IN_SECOND_DC:
startCacheServer(DC.FIRST);
stopCacheServer(DC.SECOND);
break;
}
switch(authServers) {
case ALL_NODES_IN_EVERY_DC:
forAllBackendNodes(CrossDCTestEnricher::startAuthServerBackendNode);
break;
case FIRST_NODE_IN_EVERY_DC:
DC.validDcsStream().forEach((DC dc) -> startAuthServerBackendNode(dc, 0));
break;
case FIRST_NODE_IN_FIRST_DC:
startAuthServerBackendNode(DC.FIRST, 0);
break;
case ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC:
forAllBackendNodesInDc(DC.FIRST, CrossDCTestEnricher::startAuthServerBackendNode);
startAuthServerBackendNode(DC.SECOND, 0);
break;
case ALL_NODES_IN_FIRST_DC_NO_NODES_IN_SECOND_DC:
forAllBackendNodesInDc(DC.FIRST, CrossDCTestEnricher::startAuthServerBackendNode);
break;
}
suspendPeriodicTasks();
}
use of org.keycloak.testsuite.arquillian.annotation.InitialDcState in project keycloak by keycloak.
the class ActionTokenCrossDCTest method sendResetPasswordEmailSuccessWorksInCrossDc.
@Test
@InitialDcState(authServers = ServerSetup.ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC)
public void sendResetPasswordEmailSuccessWorksInCrossDc(@JmxInfinispanCacheStatistics(dc = DC.FIRST, dcNodeIndex = 0, cacheName = InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node0Statistics, @JmxInfinispanCacheStatistics(dc = DC.FIRST, dcNodeIndex = 1, cacheName = InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc0Node1Statistics, @JmxInfinispanCacheStatistics(dc = DC.SECOND, dcNodeIndex = 0, cacheName = InfinispanConnectionProvider.ACTION_TOKEN_CACHE) InfinispanStatistics cacheDc1Node0Statistics, @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
log.debug("--DC: START sendResetPasswordEmailSuccessWorksInCrossDc");
// KEYCLOAK-17584: Temporarily disable the test for 'community' profile till KEYCLOAK-17628 isn't fixed. In other words till:
// * The test is either rewritten to start using the new Wildfly subsystem for base metrics introduced in Wildfly 22,
// * Or Keycloak is able to load the Eclipse MicroProfile Metrics subsystem from the microprofile Galleon feature-pack
Assume.assumeTrue("Ignoring test as product profile is not enabled", Profile.getName().equals("product"));
cacheDc0Node1Statistics.waitToBecomeAvailable(10, TimeUnit.SECONDS);
Comparable originalNumberOfEntries = cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY);
log.infof("Before creating user. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
UserRepresentation userRep = new UserRepresentation();
userRep.setEnabled(true);
userRep.setUsername("user1");
userRep.setEmail("user1@test.com");
String id = createUser(userRep);
UserResource user = realm.users().get(id);
List<String> actions = new LinkedList<>();
actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name());
user.executeActionsEmail(actions);
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
log.infof("After sending email. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
MimeMessage message = greenMail.getReceivedMessages()[0];
String link = MailUtils.getPasswordResetEmailLink(message);
assertSingleStatistics(cacheDc0Node0Statistics, Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY, () -> driver.navigate().to(link), Matchers::is);
log.infof("After click to the link from email. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
proceedPage.assertCurrent();
proceedPage.clickProceedLink();
passwordUpdatePage.assertCurrent();
log.infof("After open password update page. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
// Verify that there was at least one message sent via the channel - Even if we did the change on DC0, the message may be sent either from DC0 or DC1. Seems it depends on the actionTokens key ownership.
// In case that it was sent from DC1, we will receive it in DC0.
assertStatistics(channelStatisticsCrossDc, () -> {
passwordUpdatePage.changePassword("new-pass", "new-pass");
}, (Map<String, Object> oldStats, Map<String, Object> newStats) -> {
int oldSent = ((Number) oldStats.get(Constants.STAT_CHANNEL_SENT_MESSAGES)).intValue();
int newSent = ((Number) newStats.get(Constants.STAT_CHANNEL_SENT_MESSAGES)).intValue();
int oldReceived = ((Number) oldStats.get(Constants.STAT_CHANNEL_RECEIVED_MESSAGES)).intValue();
int newReceived = ((Number) newStats.get(Constants.STAT_CHANNEL_RECEIVED_MESSAGES)).intValue();
log.infof("oldSent: %d, newSent: %d, oldReceived: %d, newReceived: %d", oldSent, newSent, oldReceived, newReceived);
Assert.assertTrue(newSent - oldSent > 0 || newReceived - oldReceived > 0);
});
assertThat(PageUtils.getPageTitle(driver), containsString("Your account has been updated."));
log.infof("After update password. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
// Verify that there was an action token added in the node which was targetted by the link
assertThat(cacheDc0Node0Statistics.getSingleStatistics(Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY), greaterThan(originalNumberOfEntries));
disableDcOnLoadBalancer(DC.FIRST);
enableDcOnLoadBalancer(DC.SECOND);
// Make sure that after going to the link, the invalidated action token has been retrieved from Infinispan server cluster in the other DC
// NOTE: Using STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY as it doesn't contain the items from cacheLoader (remoteCache) until they are really loaded into the cache memory. That's the
// statistic, which is actually increased on dc1-node0 once the used actionToken is loaded to the cache (memory) from remoteCache
assertSingleStatistics(cacheDc1Node0Statistics, Constants.STAT_CACHE_NUMBER_OF_ENTRIES_IN_MEMORY, () -> driver.navigate().to(link), Matchers::greaterThan);
log.infof("After another click to the invalid link. %s", dumpNumberOfEntriesInMemory(cacheDc0Node0Statistics, cacheDc0Node1Statistics, cacheDc1Node0Statistics));
errorPage.assertCurrent();
log.debug("--DC: END sendResetPasswordEmailSuccessWorksInCrossDc");
}
use of org.keycloak.testsuite.arquillian.annotation.InitialDcState in project keycloak by keycloak.
the class SessionExpirationCrossDCTest method testLogoutUserWithFailover.
@Test
@InitialDcState(authServers = ServerSetup.ALL_NODES_IN_FIRST_DC_FIRST_NODE_IN_SECOND_DC)
public void testLogoutUserWithFailover(@JmxInfinispanCacheStatistics(dc = DC.FIRST, managementPortProperty = "cache.server.management.port", cacheName = InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc1Statistics, @JmxInfinispanCacheStatistics(dc = DC.SECOND, managementPortProperty = "cache.server.2.management.port", cacheName = InfinispanConnectionProvider.USER_SESSION_CACHE_NAME) InfinispanStatistics cacheDc2Statistics, @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
// Don't include remote stats. Size is smaller because of distributed cache
List<OAuthClient.AccessTokenResponse> responses = createInitialSessions(InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME, false, cacheDc1Statistics, cacheDc2Statistics, false);
// Kill node2 now. Around 10 sessions (half of SESSIONS_COUNT) will be lost on Keycloak side. But not on infinispan side
CrossDCTestEnricher.stopAuthServerBackendNode(DC.FIRST, 1);
// Assert it's still possible to refresh tokens. UserSessions, which were cleared from the Keycloak node, should be downloaded from remoteStore
int i1 = 0;
for (OAuthClient.AccessTokenResponse response : responses) {
i1++;
OAuthClient.AccessTokenResponse refreshTokenResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
Assert.assertNotNull("Failed in iteration " + i1, refreshTokenResponse.getRefreshToken());
Assert.assertNull("Failed in iteration " + i1, refreshTokenResponse.getError());
}
channelStatisticsCrossDc.reset();
// Increase offset a bit to ensure logout happens later then token issued time
setTimeOffset(10);
// Logout user
ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test").logout();
// Another increase after notBefore set
setTimeOffset(10);
// Assert it's not possible to refresh sessions. Works because user.notBefore
AtomicInteger i = new AtomicInteger(0);
Retry.execute(() -> {
i.incrementAndGet();
int j = 0;
for (OAuthClient.AccessTokenResponse response : responses) {
j++;
OAuthClient.AccessTokenResponse refreshTokenResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password");
Assert.assertNull("Failed in iteration " + j, refreshTokenResponse.getRefreshToken());
Assert.assertNotNull("Failed in iteration " + j, refreshTokenResponse.getError());
}
log.infof("Passed the testLogoutUserWithFailover in the iteration: %d", i.get());
}, 50, 50);
}
Aggregations