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;
}
}