blob: 32d33ae7d43ba4570098b6dcb23bc88ba1f94605 [file] [log] [blame]
package de.ids_mannheim.korap.web.controller;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Set;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.http.entity.ContentType;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
import de.ids_mannheim.korap.utils.JsonUtils;
/**
* @author margaretha
*/
public class OAuth2ControllerTest extends OAuth2TestBase {
@Autowired
public FullConfiguration config;
public String userAuthHeader;
public OAuth2ControllerTest() throws KustvaktException {
userAuthHeader = HttpAuthorizationHandler.createBasicAuthorizationHeaderValue("dory", "password");
}
@Test
public void testAuthorizeConfidentialClient() throws KustvaktException {
// with registered redirect URI
Response response = requestAuthorizationCode("code", confidentialClientId, "", "match_info search client_info", state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
URI redirectUri = response.getLocation();
MultivaluedMap<String, String> params = getQueryParamsFromURI(redirectUri);
assertNotNull(params.getFirst("code"));
assertEquals(state, params.getFirst("state"));
assertEquals(params.getFirst("scope"), "match_info search client_info");
}
@Test
public void testAuthorizePublicClient() throws KustvaktException {
// with registered redirect URI
String code = requestAuthorizationCode(publicClientId, userAuthHeader);
assertNotNull(code);
}
@Test
public void testAuthorizeWithRedirectUri() throws KustvaktException {
Response response = requestAuthorizationCode("code", publicClientId2, "https://public.com/redirect", "search match_info", "", userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
URI redirectUri = response.getLocation();
assertEquals(redirectUri.getScheme(), "https");
assertEquals(redirectUri.getHost(), "public.com");
assertEquals(redirectUri.getPath(), "/redirect");
String[] queryParts = redirectUri.getQuery().split("&");
assertTrue(queryParts[0].startsWith("code="));
assertEquals(queryParts[1], "scope=match_info+search");
}
@Test
public void testAuthorizeWithoutScope() throws KustvaktException {
Response response = requestAuthorizationCode("code", confidentialClientId, "", "", "", userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
URI redirectUri = response.getLocation();
assertEquals(redirectUri.getScheme(), "https");
assertEquals(redirectUri.getHost(), "third.party.com");
assertEquals(redirectUri.getPath(), "/confidential/redirect");
String[] queryParts = redirectUri.getQuery().split("&");
assertTrue(queryParts[0].startsWith("error_description=scope+is+required"));
assertEquals(queryParts[1], "error=invalid_scope");
}
@Test
public void testAuthorizeMissingClientId() throws KustvaktException {
Response response = requestAuthorizationCode("code", "", "", "search", "", userAuthHeader);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(node.at("/error_description").asText(), "Missing parameters: client_id");
}
@Test
public void testAuthorizeMissingRedirectUri() throws KustvaktException {
Response response = requestAuthorizationCode("code", publicClientId2, "", "search", state, userAuthHeader);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.CodeResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameter: redirect URI");
assertEquals(state, node.at("/state").asText());
}
@Test
public void testAuthorizeMissingResponseType() throws KustvaktException {
Response response = requestAuthorizationCode("", confidentialClientId, "", "search", "", userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
assertEquals("https://third.party.com/confidential/redirect?" + "error_description=Missing+parameters%3A+response_type&" + "error=invalid_request", response.getLocation().toString());
}
@Test
public void testAuthorizeMissingResponseTypeWithoutClientId() throws KustvaktException {
Response response = requestAuthorizationCode("", "", "", "search", "", userAuthHeader);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.CodeResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameters: response_type client_id");
}
@Test
public void testAuthorizeInvalidClientId() throws KustvaktException {
Response response = requestAuthorizationCode("code", "unknown-client-id", "", "search", "", userAuthHeader);
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Unknown client: unknown-client-id");
}
@Test
public void testAuthorizeDifferentRedirectUri() throws KustvaktException {
String redirectUri = "https://different.uri/redirect";
Response response = requestAuthorizationCode("code", confidentialClientId, redirectUri, "", state, userAuthHeader);
testInvalidRedirectUri(response.readEntity(String.class), response.getHeaderString("Content-Type"), true, response.getStatus());
}
@Test
public void testAuthorizeWithRedirectUriLocalhost() throws KustvaktException {
Response response = requestAuthorizationCode("code", publicClientId2, "http://localhost:1410", "search", state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
URI redirectUri = response.getLocation();
MultivaluedMap<String, String> params = getQueryParamsFromURI(redirectUri);
assertNotNull(params.getFirst("code"));
assertEquals(state, params.getFirst("state"));
assertEquals(params.getFirst("scope"), "search");
}
@Test
public void testAuthorizeWithRedirectUriFragment() throws KustvaktException {
Response response = requestAuthorizationCode("code", publicClientId2, "http://public.com/index.html#redirect", "search", state, userAuthHeader);
testInvalidRedirectUri(response.readEntity(String.class), response.getHeaderString("Content-Type"), true, response.getStatus());
}
@Test
public void testAuthorizeInvalidRedirectUri() throws KustvaktException {
// host not allowed by Apache URI Validator
String redirectUri = "https://public.uri/redirect";
Response response = requestAuthorizationCode("code", publicClientId2, redirectUri, "", state, userAuthHeader);
testInvalidRedirectUri(response.readEntity(String.class), response.getHeaderString("Content-Type"), true, response.getStatus());
}
@Test
public void testAuthorizeInvalidResponseType() throws KustvaktException {
// without redirect URI in the request
Response response = requestAuthorizationCode("string", confidentialClientId, "", "search", state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
assertEquals("https://third.party.com/confidential/redirect?" + "error_description=Invalid+response_type+parameter+" + "value&state=thisIsMyState&" + "error=invalid_request", response.getLocation().toString());
// with redirect URI, and no registered redirect URI
response = requestAuthorizationCode("string", publicClientId2, "https://public.client.com/redirect", "", state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
assertEquals("https://public.client.com/redirect?error_description=" + "Invalid+response_type+parameter+value&state=thisIsMyState&" + "error=invalid_request", response.getLocation().toString());
// with different redirect URI
String redirectUri = "https://different.uri/redirect";
response = requestAuthorizationCode("string", confidentialClientId, redirectUri, "", state, userAuthHeader);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
assertEquals(OAuthError.CodeResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Invalid redirect URI");
assertEquals(state, node.at("/state").asText());
// without redirect URI in the request and no registered
// redirect URI
response = requestAuthorizationCode("string", publicClientId2, "", "", state, userAuthHeader);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
node = JsonUtils.readTree(response.readEntity(String.class));
assertEquals(OAuthError.CodeResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameter: redirect URI");
assertEquals(state, node.at("/state").asText());
}
@Test
public void testAuthorizeInvalidScope() throws KustvaktException {
String scope = "read_address";
Response response = requestAuthorizationCode("code", confidentialClientId, "", scope, state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
assertEquals("https://third.party.com/confidential/redirect?" + "error_description=read_address+is+an+invalid+scope&" + "state=thisIsMyState&error=invalid_scope", response.getLocation().toString());
}
@Test
public void testAuthorizeUnsupportedTokenResponseType() throws KustvaktException {
Response response = requestAuthorizationCode("token", confidentialClientId, "", "search", state, userAuthHeader);
assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(), response.getStatus());
assertEquals("https://third.party.com/confidential/redirect?" + "error_description=response_type+token+is+not+" + "supported&state=thisIsMyState&error=unsupported_" + "response_type", response.getLocation().toString());
}
@Test
public void testRequestTokenAuthorizationPublic() throws KustvaktException {
String code = requestAuthorizationCode(publicClientId, userAuthHeader);
Response response = requestTokenWithAuthorizationCodeAndForm(publicClientId, clientSecret, code);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
String accessToken = node.at("/access_token").asText();
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertEquals(31536000, node.at("/expires_in").asInt());
testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
assertTrue(node.at("/refresh_token").isMissingNode());
}
@Test
public void testRequestTokenAuthorizationConfidential() throws KustvaktException {
String scope = "search";
Response response = requestAuthorizationCode("code", confidentialClientId, "", scope, state, userAuthHeader);
MultivaluedMap<String, String> params = getQueryParamsFromURI(response.getLocation());
String code = params.get("code").get(0);
String scopes = params.get("scope").get(0);
assertEquals(scopes, "search");
response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, code);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
testRequestTokenWithUsedAuthorization(code);
String refreshToken = node.at("/refresh_token").asText();
testRefreshTokenExpiry(refreshToken);
testRequestRefreshTokenInvalidScope(confidentialClientId, refreshToken);
testRequestRefreshTokenInvalidClient(refreshToken);
testRequestRefreshTokenInvalidRefreshToken(confidentialClientId);
testRequestRefreshToken(confidentialClientId, clientSecret, refreshToken);
}
private void testRequestTokenWithUsedAuthorization(String code) throws KustvaktException {
Response response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, code);
String entity = response.readEntity(String.class);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_GRANT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Invalid authorization");
}
@Test
public void testRequestTokenInvalidAuthorizationCode() throws KustvaktException {
Response response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, "blahblah");
String entity = response.readEntity(String.class);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.at("/error").asText());
}
@Test
public void testRequestTokenAuthorizationReplyAttack() throws KustvaktException {
String redirect_uri = "https://third.party.com/confidential/redirect";
String scope = "search";
Response response = requestAuthorizationCode("code", confidentialClientId, redirect_uri, scope, state, userAuthHeader);
String code = parseAuthorizationCode(response);
testRequestTokenAuthorizationInvalidClient(code);
testRequestTokenAuthorizationMissingRedirectUri(code);
testRequestTokenAuthorizationInvalidRedirectUri(code);
testRequestTokenAuthorizationRevoked(code, redirect_uri);
}
private void testRequestTokenAuthorizationInvalidClient(String code) throws KustvaktException {
Response response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, "wrong_secret", code);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
}
private void testRequestTokenAuthorizationMissingRedirectUri(String code) throws KustvaktException {
Response response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, "secret", code);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing redirect URI");
}
private void testRequestTokenAuthorizationInvalidRedirectUri(String code) throws KustvaktException {
Form tokenForm = new Form();
tokenForm.param("grant_type", "authorization_code");
tokenForm.param("client_id", confidentialClientId);
tokenForm.param("client_secret", "secret");
tokenForm.param("code", code);
tokenForm.param("redirect_uri", "https://blahblah.com");
Response response = requestToken(tokenForm);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
}
private void testRequestTokenAuthorizationRevoked(String code, String uri) throws KustvaktException {
Form tokenForm = new Form();
tokenForm.param("grant_type", "authorization_code");
tokenForm.param("client_id", confidentialClientId);
tokenForm.param("client_secret", "secret");
tokenForm.param("code", code);
tokenForm.param("redirect_uri", uri);
Response response = requestToken(tokenForm);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_GRANT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Invalid authorization");
}
@Test
public void testRequestTokenPasswordGrantConfidentialSuper() throws KustvaktException {
Response response = requestTokenWithDoryPassword(superClientId, clientSecret);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
assertEquals(node.at("/scope").asText(), "all");
String refresh = node.at("/refresh_token").asText();
RefreshToken refreshToken = refreshTokenDao.retrieveRefreshToken(refresh);
Set<AccessScope> scopes = refreshToken.getScopes();
assertEquals(1, scopes.size());
assertEquals(scopes.toString(), "[all]");
testRefreshTokenExpiry(refresh);
}
@Test
public void testRequestTokenPasswordGrantWithScope() throws KustvaktException {
String scope = "match_info search";
Form form = new Form();
form.param("grant_type", "password");
form.param("client_id", superClientId);
form.param("client_secret", clientSecret);
form.param("username", "dory");
form.param("password", "pwd");
form.param("scope", scope);
Response response = requestToken(form);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
assertEquals(scope, node.at("/scope").asText());
String refreshToken = node.at("/refresh_token").asText();
testRequestRefreshTokenWithUnauthorizedScope(superClientId, clientSecret, refreshToken, "all");
testRequestRefreshTokenWithScope(superClientId, clientSecret, refreshToken, "search");
}
@Test
public void testRequestTokenPasswordGrantConfidentialNonSuper() throws KustvaktException {
Response response = requestTokenWithDoryPassword(confidentialClientId, clientSecret);
String entity = response.readEntity(String.class);
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Password grant is not allowed for third party clients");
}
@Test
public void testRequestTokenPasswordGrantPublic() throws KustvaktException {
Response response = requestTokenWithDoryPassword(publicClientId, "");
String entity = response.readEntity(String.class);
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Password grant is not allowed for third party clients");
}
@Test
public void testRequestTokenPasswordGrantAuthorizationHeader() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "password");
form.param("client_id", superClientId);
form.param("username", "dory");
form.param("password", "password");
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.AUTHORIZATION, "Basic ZkNCYlFrQXlZekk0TnpVeE1nOnNlY3JldA==").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
}
/**
* In case, client_id is specified both in Authorization header
* and request body, client_id in the request body is ignored.
*
* @throws KustvaktException
*/
@Test
public void testRequestTokenPasswordGrantDifferentClientIds() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "password");
form.param("client_id", "9aHsGW6QflV13ixNpez");
form.param("username", "dory");
form.param("password", "password");
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.AUTHORIZATION, "Basic ZkNCYlFrQXlZekk0TnpVeE1nOnNlY3JldA==").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
}
@Test
public void testRequestTokenPasswordGrantMissingClientSecret() throws KustvaktException {
Response response = requestTokenWithDoryPassword(confidentialClientId, "");
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameter: client_secret");
}
@Test
public void testRequestTokenPasswordGrantMissingClientId() throws KustvaktException {
Response response = requestTokenWithDoryPassword(null, clientSecret);
String entity = response.readEntity(String.class);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameters: client_id");
}
@Test
public void testRequestTokenClientCredentialsGrant() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "client_credentials");
form.param("client_id", confidentialClientId);
form.param("client_secret", "secret");
Response response = requestToken(form);
String entity = response.readEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
// length?
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
}
/**
* Client credentials grant is only allowed for confidential
* clients.
*/
@Test
public void testRequestTokenClientCredentialsGrantPublic() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "client_credentials");
form.param("client_id", publicClientId);
form.param("client_secret", "");
Response response = requestToken(form);
String entity = response.readEntity(String.class);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.at("/error").asText());
assertEquals(node.at("/error_description").asText(), "Missing parameters: client_secret");
}
@Test
public void testRequestTokenClientCredentialsGrantReducedScope() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "client_credentials");
form.param("client_id", confidentialClientId);
form.param("client_secret", "secret");
form.param("scope", "preferred_username client_info");
Response response = requestToken(form);
String entity = response.readEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
// length?
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
assertEquals(node.at("/scope").asText(), "client_info");
}
@Test
public void testRequestTokenMissingGrantType() throws KustvaktException {
Form form = new Form();
Response response = requestToken(form);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.at("/error").asText());
}
@Test
public void testRequestTokenUnsupportedGrant() throws KustvaktException {
Form form = new Form();
form.param("grant_type", "blahblah");
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertEquals(node.get("error_description").asText(), "Invalid grant_type parameter value");
assertEquals(OAuthError.TokenResponse.INVALID_REQUEST, node.get("error").asText());
}
private void testRequestRefreshTokenInvalidScope(String clientId, String refreshToken) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", clientId);
form.param("client_secret", clientSecret);
form.param("refresh_token", refreshToken);
form.param("scope", "search serialize_query");
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
}
private void testRequestRefreshToken(String clientId, String clientSecret, String refreshToken) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", clientId);
form.param("client_secret", clientSecret);
form.param("refresh_token", refreshToken);
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
String newRefreshToken = node.at("/refresh_token").asText();
assertNotNull(newRefreshToken);
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
assertTrue(!newRefreshToken.equals(refreshToken));
testRequestTokenWithRevokedRefreshToken(clientId, clientSecret, refreshToken);
testRevokeToken(newRefreshToken, clientId, clientSecret, REFRESH_TOKEN_TYPE);
testRequestTokenWithRevokedRefreshToken(clientId, clientSecret, newRefreshToken);
}
private void testRequestRefreshTokenWithUnauthorizedScope(String clientId, String clientSecret, String refreshToken, String scope) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", clientId);
form.param("client_secret", clientSecret);
form.param("refresh_token", refreshToken);
form.param("scope", scope);
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
}
private void testRequestRefreshTokenWithScope(String clientId, String clientSecret, String refreshToken, String scope) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", clientId);
form.param("client_secret", clientSecret);
form.param("refresh_token", refreshToken);
form.param("scope", scope);
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
String newRefreshToken = node.at("/refresh_token").asText();
assertNotNull(newRefreshToken);
assertEquals(TokenType.BEARER.toString(), node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());
assertTrue(!newRefreshToken.equals(refreshToken));
assertEquals(scope, node.at("/scope").asText());
}
private void testRequestRefreshTokenInvalidClient(String refreshToken) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", "iBr3LsTCxOj7D2o0A5m");
form.param("refresh_token", refreshToken);
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
}
private void testRequestRefreshTokenInvalidRefreshToken(String clientId) throws KustvaktException {
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", clientId);
form.param("client_secret", clientSecret);
form.param("refresh_token", "Lia8s8w8tJeZSBlaQDrYV8ion3l");
Response response = target().path(API_VERSION).path("oauth2").path("token").request().header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
}
private JsonNode requestTokenList(String userAuthHeader, String tokenType, String clientId) throws KustvaktException {
Form form = new Form();
form.param("super_client_id", superClientId);
form.param("super_client_secret", clientSecret);
form.param("token_type", tokenType);
if (clientId != null && !clientId.isEmpty()) {
form.param("client_id", clientId);
}
Response response = target().path(API_VERSION).path("oauth2").path("token").path("list").request().header(Attributes.AUTHORIZATION, userAuthHeader).header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED).post(Entity.form(form));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
String entity = response.readEntity(String.class);
return JsonUtils.readTree(entity);
}
private JsonNode requestTokenList(String userAuthHeader, String tokenType) throws KustvaktException {
return requestTokenList(userAuthHeader, tokenType, null);
}
@Test
public void testListRefreshTokenConfidentialClient() throws KustvaktException {
String username = "gurgle";
String password = "pwd";
userAuthHeader = HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(username, password);
// super client
Response response = requestTokenWithPassword(superClientId, clientSecret, username, password);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String refreshToken1 = node.at("/refresh_token").asText();
// client 1
String code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
// client 2
code = requestAuthorizationCode(confidentialClientId2, clientRedirectUri, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId2, clientSecret, code, clientRedirectUri);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
// list
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(2, node.size());
assertEquals(confidentialClientId, node.at("/0/client_id").asText());
assertEquals(confidentialClientId2, node.at("/1/client_id").asText());
// client 1
code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
// another user
String darlaAuthHeader = HttpAuthorizationHandler.createBasicAuthorizationHeaderValue("darla", "pwd");
// test listing clients
node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(0, node.size());
// client 1
code = requestAuthorizationCode(confidentialClientId, darlaAuthHeader);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
response = requestTokenWithAuthorizationCodeAndForm(confidentialClientId, clientSecret, code);
node = JsonUtils.readTree(response.readEntity(String.class));
String refreshToken5 = node.at("/refresh_token").asText();
// list all refresh tokens
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(3, node.size());
// list refresh tokens from client 1
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE, confidentialClientId);
assertEquals(2, node.size());
testRevokeToken(refreshToken1, superClientId, clientSecret, REFRESH_TOKEN_TYPE);
testRevokeToken(node.at("/0/token").asText(), confidentialClientId, clientSecret, REFRESH_TOKEN_TYPE);
testRevokeToken(node.at("/1/token").asText(), confidentialClientId2, clientSecret, REFRESH_TOKEN_TYPE);
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(1, node.size());
testRevokeTokenViaSuperClient(node.at("/0/token").asText(), userAuthHeader);
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(0, node.size());
// try revoking a token belonging to another user
// should not return any errors
testRevokeTokenViaSuperClient(refreshToken5, userAuthHeader);
node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(1, node.size());
testRevokeTokenViaSuperClient(refreshToken5, darlaAuthHeader);
node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(0, node.size());
}
@Test
public void testListTokenPublicClient() throws KustvaktException {
String username = "nemo";
String password = "pwd";
userAuthHeader = HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(username, password);
// access token 1
String code = requestAuthorizationCode(publicClientId, userAuthHeader);
Response response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "", code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String accessToken1 = node.at("/access_token").asText();
// access token 2
code = requestAuthorizationCode(publicClientId, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "", code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = JsonUtils.readTree(response.readEntity(String.class));
String accessToken2 = node.at("/access_token").asText();
// list access tokens
node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
assertEquals(2, node.size());
// list refresh tokens
node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
assertEquals(0, node.size());
testRevokeTokenViaSuperClient(accessToken1, userAuthHeader);
node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
// System.out.println(node);
assertEquals(1, node.size());
assertEquals(accessToken2, node.at("/0/token").asText());
assertTrue(node.at("/0/scope").size() > 0);
assertNotNull(node.at("/0/created_date").asText());
assertNotNull(node.at("/0/expires_in").asLong());
assertNotNull(node.at("/0/user_authentication_time").asText());
assertEquals(publicClientId, node.at("/0/client_id").asText());
assertNotNull(node.at("/0/client_name").asText());
assertNotNull(node.at("/0/client_description").asText());
assertNotNull(node.at("/0/client_url").asText());
testRevokeTokenViaSuperClient(accessToken2, userAuthHeader);
node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
assertEquals(0, node.size());
}
private void testRefreshTokenExpiry(String refreshToken) throws KustvaktException {
RefreshToken token = refreshTokenDao.retrieveRefreshToken(refreshToken);
ZonedDateTime expiry = token.getCreatedDate().plusSeconds(config.getRefreshTokenLongExpiry());
assertTrue(expiry.equals(token.getExpiryDate()));
}
}