package org.keycloak.testsuite.adapter.servlet;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
import org.keycloak.testsuite.adapter.page.OfflineToken;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.util.TokenUtil;
import org.openqa.selenium.By;
import javax.ws.rs.core.UriBuilder;
import java.util.List;
import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.WaitUtils.pause;
import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
*/
public abstract class AbstractOfflineServletsAdapterTest extends AbstractServletsAdapterTest {
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected OfflineToken offlineTokenPage;
@Page
protected LoginPage loginPage;
@Page
protected AccountApplicationsPage accountAppPage;
@Page
protected OAuthGrantPage oauthGrantPage;
@Deployment(name = OfflineToken.DEPLOYMENT_NAME)
protected static WebArchive offlineClient() {
return servletDeployment(OfflineToken.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, OfflineTokenServlet.class, ErrorServlet.class, ServletTestUtils.class);
}
@Override
public void setDefaultPageUriParameters() {
super.setDefaultPageUriParameters();
testRealmPage.setAuthRealm(TEST);
testRealmLoginPage.setAuthRealm(TEST);
}
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(loadRealm("/adapter-test/offline-client/offlinerealm.json"));
}
@Test
public void testServlet() throws Exception {
String servletUri = UriBuilder.fromUri(offlineTokenPage.toString())
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
.build().toString();
driver.navigate().to(servletUri);
waitUntilElement(By.tagName("body")).is().visible();
loginPage.login("test-user@localhost", "password");
assertCurrentUrlStartsWith(offlineTokenPage);
Assert.assertEquals(offlineTokenPage.getRefreshToken().getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
Assert.assertEquals(offlineTokenPage.getRefreshToken().getExpiration(), 0);
String accessTokenId = offlineTokenPage.getAccessToken().getId();
String refreshTokenId = offlineTokenPage.getRefreshToken().getId();
setAdapterAndServerTimeOffset(9999);
offlineTokenPage.navigateTo();
assertCurrentUrlStartsWith(offlineTokenPage);
Assert.assertNotEquals(offlineTokenPage.getRefreshToken().getId(), refreshTokenId);
Assert.assertNotEquals(offlineTokenPage.getAccessToken().getId(), accessTokenId);
// Ensure that logout works for webapp (even if offline token will be still valid in Keycloak DB)
offlineTokenPage.logout();
assertCurrentUrlDoesntStartWith(offlineTokenPage);
loginPage.assertCurrent();
offlineTokenPage.navigateTo();
assertCurrentUrlDoesntStartWith(offlineTokenPage);
loginPage.assertCurrent();
setAdapterAndServerTimeOffset(0);
events.clear();
}
@Test
public void testServletWithRevoke() {
// Login to servlet first with offline token
String servletUri = UriBuilder.fromUri(offlineTokenPage.toString())
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
.build().toString();
driver.navigate().to(servletUri);
waitUntilElement(By.tagName("body")).is().visible();
loginPage.login("test-user@localhost", "password");
assertCurrentUrlStartsWith(offlineTokenPage);
Assert.assertEquals(offlineTokenPage.getRefreshToken().getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
// Assert refresh works with increased time
setAdapterAndServerTimeOffset(9999);
offlineTokenPage.navigateTo();
assertCurrentUrlStartsWith(offlineTokenPage);
setAdapterAndServerTimeOffset(0);
events.clear();
// Go to account service and revoke grant
accountAppPage.open();
List<String> additionalGrants = accountAppPage.getApplications().get("offline-client").getAdditionalGrants();
Assert.assertEquals(additionalGrants.size(), 1);
Assert.assertEquals(additionalGrants.get(0), "Offline Token");
accountAppPage.revokeGrant("offline-client");
pause(500);
Assert.assertEquals(accountAppPage.getApplications().get("offline-client").getAdditionalGrants().size(), 0);
events.expect(EventType.REVOKE_GRANT)
.client("account").detail(Details.REVOKED_CLIENT, "offline-client").assertEvent();
// Assert refresh doesn't work now (increase time one more time)
setAdapterAndServerTimeOffset(9999);
offlineTokenPage.navigateTo();
assertCurrentUrlDoesntStartWith(offlineTokenPage);
loginPage.assertCurrent();
setAdapterAndServerTimeOffset(0);
}
@Test
public void testServletWithConsent() {
ClientManager.realm(adminClient.realm("test")).clientId("offline-client").consentRequired(true);
// Assert grant page doesn't have 'Offline Access' role when offline token is not requested
offlineTokenPage.navigateTo();
loginPage.login("test-user@localhost", "password");
oauthGrantPage.assertCurrent();
waitUntilElement(By.xpath("//body")).text().not().contains("Offline access");
oauthGrantPage.cancel();
// Assert grant page has 'Offline Access' role now
String servletUri = UriBuilder.fromUri(offlineTokenPage.toString())
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
.build().toString();
driver.navigate().to(servletUri);
waitUntilElement(By.tagName("body")).is().visible();
loginPage.login("test-user@localhost", "password");
oauthGrantPage.assertCurrent();
waitUntilElement(By.xpath("//body")).text().contains("Offline access");
oauthGrantPage.accept();
assertCurrentUrlStartsWith(offlineTokenPage);
Assert.assertEquals(offlineTokenPage.getRefreshToken().getType(), TokenUtil.TOKEN_TYPE_OFFLINE);
accountAppPage.open();
AccountApplicationsPage.AppEntry offlineClient = accountAppPage.getApplications().get("offline-client");
Assert.assertTrue(offlineClient.getRolesGranted().contains("Offline access"));
Assert.assertTrue(offlineClient.getAdditionalGrants().contains("Offline Token"));
//This was necessary to be introduced, otherwise other testcases will fail
offlineTokenPage.logout();
assertCurrentUrlDoesntStartWith(offlineTokenPage);
loginPage.assertCurrent();
events.clear();
// Revert change
ClientManager.realm(adminClient.realm("test")).clientId("offline-client").consentRequired(false);
}
private void setAdapterAndServerTimeOffset(int timeOffset) {
super.setAdapterAndServerTimeOffset(timeOffset, offlineTokenPage.toString());
}
}