Search in sources :

Example 1 with InitialDcState

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");
}
Also used : MimeMessage(javax.mail.internet.MimeMessage) UserResource(org.keycloak.admin.client.resource.UserResource) Matchers.containsString(org.hamcrest.Matchers.containsString) LinkedList(java.util.LinkedList) UserRepresentation(org.keycloak.representations.idm.UserRepresentation) Test(org.junit.Test) InitialDcState(org.keycloak.testsuite.arquillian.annotation.InitialDcState)

Example 2 with InitialDcState

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();
}
Also used : ServerSetup(org.keycloak.testsuite.crossdc.ServerSetup) InitialDcState(org.keycloak.testsuite.arquillian.annotation.InitialDcState) DC(org.keycloak.testsuite.crossdc.DC)

Example 3 with InitialDcState

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");
}
Also used : MimeMessage(javax.mail.internet.MimeMessage) UserResource(org.keycloak.admin.client.resource.UserResource) Matchers.containsString(org.hamcrest.Matchers.containsString) Matchers(org.hamcrest.Matchers) Map(java.util.Map) LinkedList(java.util.LinkedList) UserRepresentation(org.keycloak.representations.idm.UserRepresentation) Test(org.junit.Test) InitialDcState(org.keycloak.testsuite.arquillian.annotation.InitialDcState)

Example 4 with InitialDcState

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);
}
Also used : OAuthClient(org.keycloak.testsuite.util.OAuthClient) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Test(org.junit.Test) InitialDcState(org.keycloak.testsuite.arquillian.annotation.InitialDcState)

Aggregations

InitialDcState (org.keycloak.testsuite.arquillian.annotation.InitialDcState)4 Test (org.junit.Test)3 LinkedList (java.util.LinkedList)2 MimeMessage (javax.mail.internet.MimeMessage)2 Matchers.containsString (org.hamcrest.Matchers.containsString)2 UserResource (org.keycloak.admin.client.resource.UserResource)2 UserRepresentation (org.keycloak.representations.idm.UserRepresentation)2 Map (java.util.Map)1 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)1 Matchers (org.hamcrest.Matchers)1 DC (org.keycloak.testsuite.crossdc.DC)1 ServerSetup (org.keycloak.testsuite.crossdc.ServerSetup)1 OAuthClient (org.keycloak.testsuite.util.OAuthClient)1