package bo.gotthardt.oauth2; import bo.gotthardt.model.OAuth2AccessToken; import bo.gotthardt.model.User; import bo.gotthardt.oauth2.authentication.UserAuthenticator; import bo.gotthardt.oauth2.authorization.OAuth2AccessTokenResource; import bo.gotthardt.oauth2.authorization.OAuth2AuthorizationRequestFactory; import bo.gotthardt.oauth2.authorization.UserAuthorizer; import bo.gotthardt.test.ApiIntegrationTest; import bo.gotthardt.user.UserResource; import com.google.common.net.HttpHeaders; import io.dropwizard.auth.AuthDynamicFeature; import io.dropwizard.auth.AuthValueFactoryProvider; import io.dropwizard.auth.oauth.OAuthCredentialAuthFilter; import io.dropwizard.testing.junit.ResourceTestRule; import org.glassfish.jersey.internal.util.collection.MultivaluedStringMap; import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature; import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import javax.ws.rs.client.Invocation; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.time.Duration; import static bo.gotthardt.test.assertj.DropwizardAssertions.assertThat; /** * Tests for the OAuth2 functionality working end-to-end. * * @author Bo Gotthardt */ public class OAuth2IntegrationTest extends ApiIntegrationTest { @ClassRule public static final ResourceTestRule resources = ResourceTestRule.builder() .addResource(new OAuth2AccessTokenResource(db)) .addResource(new UserResource(db)) .addResource(OAuth2AuthorizationRequestFactory.getBinder()) .addResource(RolesAllowedDynamicFeature.class) .addResource(new AuthValueFactoryProvider.Binder<>(User.class)) .addResource(new AuthDynamicFeature( new OAuthCredentialAuthFilter.Builder<User>() .setAuthenticator(new UserAuthenticator(db)) .setAuthorizer(new UserAuthorizer()) .setPrefix("Bearer") .buildAuthFilter())) .setMapper(getMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .build(); private User user; @Before public void setup() { db.clear(); user = new User("testuser", "testpass", "Testuser"); db.save(user); } @Test public void shouldCreateAndSendTokenThatIdentifiesUser() { Response response = POST("/token", loginParameters("testuser", "testpass")); assertThat(response).hasStatus(Response.Status.OK); OAuth2AccessToken token = response.readEntity(OAuth2AccessToken.class); // The token sent in the response won't have any user information, but if we get it from the database it will have. assertThat(db.find(OAuth2AccessToken.class, token.getAccessToken()).getUser().getId()) .isEqualTo(user.getId()); } @Test public void shouldNotCreateTokenForInvalidCredentials() { assertThat(POST("/token", loginParameters("testuser", "WRONGPASSWORD"))) .hasStatus(Response.Status.UNAUTHORIZED); assertThat(db.find(OAuth2AccessToken.class).findRowCount()) .isEqualTo(0); } @Test public void shouldNotCreateTokenForNonexistentCredentials() { assertThat(POST("/token", loginParameters("DOESNOTEXIST", "testpass"))) .hasStatus(Response.Status.UNAUTHORIZED); assertThat(db.find(OAuth2AccessToken.class).findRowCount()) .isEqualTo(0); } @Test public void should400WhenMissingGrantType() { assertThat(POST("/token", null)) .hasStatus(Response.Status.BAD_REQUEST); } @Test public void should400WhenGrantTypePasswordIsMissingUsername() { assertThat(POST("/token", loginParameters(null, "testpass"))) .hasStatus(Response.Status.BAD_REQUEST); } @Test public void should400WhenGrantTypePasswordIsMissingPassword() { assertThat(POST("/token", loginParameters("testuser", null))) .hasStatus(Response.Status.BAD_REQUEST); } @Test public void shouldRefuseNonAuthorizedAccessToAuthProtectedResource() { assertThat(GET("/users/" + user.getId())) .hasStatus(Response.Status.UNAUTHORIZED); } @Test public void shouldRefuseUnauthorizedAccessToAuthProtectedResource() { Response response = target("/users/" + user.getId()).request() .header(HttpHeaders.AUTHORIZATION, "Bearer WRONGTOKEN") .get(); assertThat(response) .hasStatus(Response.Status.UNAUTHORIZED); } @Test public void shouldAllowAuthorizedAccessToProtectedResource() { OAuth2AccessToken token = POST("/token", loginParameters("testuser", "testpass")) .readEntity(OAuth2AccessToken.class); Invocation.Builder header = target("/users/" + user.getId()).request() .header(HttpHeaders.AUTHORIZATION, "Bearer " + token.getAccessToken()); Response response = header.get(); assertThat(response) .hasStatus(Response.Status.OK) .hasJsonContent(user); } @Test public void shouldInvalidateTokens() { OAuth2AccessToken token = new OAuth2AccessToken(user, Duration.ofHours(1)); db.save(token); target("/token").request() .header(HttpHeaders.AUTHORIZATION, "Bearer " + token.getAccessToken()) .delete(); db.refresh(token); assertThat(token.isValid()).isFalse(); } @Override public ResourceTestRule getResources() { return resources; } private static MultivaluedMap<String, String> loginParameters(String username, String password) { MultivaluedMap<String, String> parameters = new MultivaluedStringMap(); parameters.add("grant_type", "password"); if (username != null) { parameters.add("username", username); } if (password != null) { parameters.add("password", password); } return parameters; } }