Implemented OAuth2 client info controller, super clients and unlimited
authorization scopes. Enabled using Bearer tokens as user authentication
methods for many controllers including OAuth2 controllers.
Change-Id: I1043164acbe49501210a6fca7f4531d110eb81a5
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
index 98f3434..78d91aa 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
@@ -2,22 +2,27 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
import java.io.IOException;
+import java.net.URI;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import org.apache.http.entity.ContentType;
import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
import org.junit.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.core.util.MultivaluedMapImpl;
-import de.ids_mannheim.korap.authentication.http.TransferEncoding;
+import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.SpringJerseyTest;
import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -27,15 +32,48 @@
public class OAuth2AccessTokenTest extends SpringJerseyTest {
- private String clientId = "fCBbQkAyYzI4NzUxMg";
+ // normal client
+ private String clientId = "9aHsGW6QflV13ixNpez";
+ private String superClientId = "fCBbQkAyYzI4NzUxMg";
+ private String clientSecret = "secret";
- private JsonNode requestToken () throws KustvaktException {
+ private String requestAuthorizationCode (String scope, String authHeader)
+ throws KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
- form.add("grant_type", "password");
+ form.add("response_type", "code");
form.add("client_id", clientId);
- form.add("client_secret", "secret");
- form.add("username", "dory");
- form.add("password", "password");
+ form.add("client_secret", clientSecret);
+ if (scope != null) {
+ form.add("scope", scope);
+ }
+
+ ClientResponse response = resource().path("oauth2").path("authorize")
+ .header(Attributes.AUTHORIZATION, authHeader)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ return params.getFirst("code");
+ }
+
+ // client credentials as form params
+ private JsonNode requestTokenWithAuthorizationCodeGrant ()
+ throws KustvaktException {
+ String authHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ String code = requestAuthorizationCode(null, authHeader);
+
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("grant_type", "authorization_code");
+ form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
+ form.add("code", code);
ClientResponse response = resource().path("oauth2").path("token")
.header(HttpHeaders.CONTENT_TYPE,
@@ -43,45 +81,85 @@
.entity(form).post(ClientResponse.class);
String entity = response.getEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
return node;
}
- @Test
- public void testListVCScope () throws KustvaktException {
+ // client credentials in authorization header
+ private JsonNode requestTokenWithAuthorizationHeader (String code)
+ throws KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
- form.add("grant_type", "password");
- form.add("client_id", "fCBbQkAyYzI4NzUxMg");
- form.add("client_secret", "secret");
- form.add("username", "dory");
- form.add("password", "password");
- form.add("scope", OAuth2Scope.VC_INFO.toString());
+ form.add("grant_type", "authorization_code");
+ form.add("client_id", clientId);
+ form.add("code", code);
ClientResponse response = resource().path("oauth2").path("token")
- .header(HttpHeaders.AUTHORIZATION,
- "Bearer" + TransferEncoding
- .encodeBase64("fCBbQkAyYzI4NzUxMg", "secret"))
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(clientId,
+ clientSecret))
.header(HttpHeaders.CONTENT_TYPE,
ContentType.APPLICATION_FORM_URLENCODED)
.entity(form).post(ClientResponse.class);
String entity = response.getEntity(String.class);
- JsonNode node = JsonUtils.readTree(entity);
- String token = node.at("/access_token").asText();
+ return JsonUtils.readTree(entity);
+ }
- response = resource().path("vc").path("list")
+ @Test
+ public void testScopeWithSuperClient () throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("grant_type", "password");
+ form.add("client_id", superClientId);
+ form.add("client_secret", clientSecret);
+ form.add("username", "dory");
+ form.add("password", "password");
+
+ ClientResponse response = resource().path("oauth2").path("token")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals("all", node.at("/scope").asText());
+ String accessToken = node.at("/access_token").asText();
+
+ // test list user group
+ response = resource().path("group").path("list")
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .get(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals(2, node.size());
+ }
+
+ @Test
+ public void testCustomAuthorizationScope () throws KustvaktException {
+ String authHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory", "password");
+ String code = requestAuthorizationCode(OAuth2Scope.VC_INFO.toString(),
+ authHeader);
+ JsonNode node = requestTokenWithAuthorizationHeader(code);
+
+ String token = node.at("/access_token").asText();
+ assertTrue(node.at("/scope").asText()
+ .contains(OAuth2Scope.VC_INFO.toString()));
+
+ ClientResponse response = resource().path("vc").path("list")
.header(Attributes.AUTHORIZATION, "Bearer " + token)
.get(ClientResponse.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
- entity = response.getEntity(String.class);
- node = JsonUtils.readTree(entity);
+ node = JsonUtils.readTree(response.getEntity(String.class));
assertEquals(4, node.size());
}
@Test
- public void testTokenAccessScope () throws KustvaktException, IOException {
- String accessToken = requestToken().at("/access_token").asText();
+ public void testDefaultScope () throws KustvaktException, IOException {
+ String accessToken = requestTokenWithAuthorizationCodeGrant()
+ .at("/access_token").asText();
testListVCScopeNotAuthorized(accessToken);
testListVCAccessBearerNotAuthorize(accessToken);
testSearchWithOAuth2Token(accessToken);
@@ -116,7 +194,7 @@
JsonNode node = JsonUtils.readTree(entity);
assertEquals(StatusCodes.AUTHORIZATION_FAILED,
node.at("/errors/0/0").asInt());
- assertEquals("Token type Bearer is not allowed",
+ assertEquals("Scope vc_access_info is not authorized",
node.at("/errors/0/1").asText());
}
@@ -161,10 +239,11 @@
@Test
public void testRevokeAccessTokenConfidentialClient ()
throws KustvaktException {
- String accessToken = requestToken().at("/access_token").asText();
+ String accessToken = requestTokenWithAuthorizationCodeGrant()
+ .at("/access_token").asText();
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("token", accessToken);
- form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.add("client_id", clientId);
form.add("client_secret", "secret");
ClientResponse response = resource().path("oauth2").path("revoke")
@@ -200,10 +279,10 @@
@Test
public void testRevocationAfterRequestRefreshToken ()
throws KustvaktException {
- JsonNode node = requestToken();
+ JsonNode node = requestTokenWithAuthorizationCodeGrant();
String accessToken = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
-
+
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", GrantType.REFRESH_TOKEN.toString());
form.add("client_id", clientId);
@@ -221,8 +300,65 @@
node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
- assertEquals(refreshToken,node.at("/refresh_token").asText());
-
+ assertEquals(refreshToken, node.at("/refresh_token").asText());
+
testSearchWithRevokedToken(accessToken);
}
+
+ @Test
+ public void testRequestAuthorizationWithBearerTokenUnauthorized () throws KustvaktException {
+ String userAuthToken = requestTokenWithAuthorizationCodeGrant()
+ .at("/access_token").asText();
+
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("response_type", "code");
+ form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
+
+ ClientResponse response = resource().path("oauth2").path("authorize")
+ .header(Attributes.AUTHORIZATION, "Bearer " + userAuthToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Scope authorize is not authorized",
+ node.at("/errors/0/1").asText());
+ }
+
+ @Test
+ public void testRequestAuthorizationWithBearerToken ()
+ throws KustvaktException {
+ String userAuthToken = requestTokenWithPasswordGrant();
+ String code = requestAuthorizationCode(null, "Bearer " + userAuthToken);
+ assertNotNull(code);
+ }
+
+ private String requestTokenWithPasswordGrant () throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("grant_type", "password");
+ form.add("client_id", superClientId);
+ form.add("client_secret", clientSecret);
+ form.add("username", "dory");
+ form.add("password", "password");
+
+ ClientResponse response = resource().path("oauth2").path("token")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+ String entity = response.getEntity(String.class);
+
+ JsonNode node = JsonUtils.readTree(entity);
+ String token = node.at("/access_token").asText();
+ assertNotNull(token);
+ assertEquals(TokenType.BEARER.toString(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+ return token;
+ }
}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index ffecbb3..9c3d536 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -94,7 +94,6 @@
testDeregisterConfidentialClient(clientId, newclientSecret);
}
-
private void testRegisterClientNonUniqueURL () throws KustvaktException {
ClientResponse response = registerConfidentialClient();
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -127,9 +126,8 @@
assertNotNull(clientId);
assertTrue(node.at("/client_secret").isMissingNode());
- testDeregisterPublicClientMissingUserAuthentication(clientId);
- testDeregisterPublicClientMissingId();
- testDeregisterPublicClient(clientId);
+ testResetPublicClientSecret(clientId);
+ testAccessTokenAfterDeregistration(clientId);
}
@Test
@@ -155,35 +153,9 @@
assertNotNull(clientId);
assertTrue(node.at("/client_secret").isMissingNode());
- testResetPublicClientSecret(clientId);
- }
-
- @Test
- public void testRegisterNativeClient () throws UniformInterfaceException,
- ClientHandlerException, KustvaktException {
- OAuth2ClientJson json = new OAuth2ClientJson();
- json.setName("NativeClient");
- json.setType(OAuth2ClientType.PUBLIC);
- json.setUrl("http://korap.ids-mannheim.de/native");
- json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
- json.setDescription("This is a native test client.");
-
- ClientResponse response = resource().path("oauth2").path("client")
- .path("register")
- .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
- .createBasicAuthorizationHeaderValue(username, "pass"))
- .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
- .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
- .entity(json).post(ClientResponse.class);
-
- String entity = response.getEntity(String.class);
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
- JsonNode node = JsonUtils.readTree(entity);
- String clientId = node.at("/client_id").asText();
-
- // EM: need to check native
-
- testAccessTokenAfterDeregistration(clientId);
+ testDeregisterPublicClientMissingUserAuthentication(clientId);
+ testDeregisterPublicClientMissingId();
+ testDeregisterPublicClient(clientId);
}
private void testAccessTokenAfterDeregistration (String clientId)
@@ -420,4 +392,55 @@
return newClientSecret;
}
+
+ @Test
+ public void testUpdateClientPrivilege () throws KustvaktException {
+ ClientResponse response = registerConfidentialClient();
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ String clientId = node.at("/client_id").asText();
+
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("client_id", clientId);
+ form.add("super", "true");
+
+ updateClientPrivilege(form);
+ node = retrieveClientInfo(clientId, "admin");
+ assertTrue(node.at("/isSuper").asBoolean());
+
+ form.remove("super");
+ form.add("super", "false");
+ updateClientPrivilege(form);
+ node = retrieveClientInfo(clientId, username);
+ assertTrue(node.at("/isSuper").isMissingNode());
+
+ }
+
+ private void updateClientPrivilege (MultivaluedMap<String, String> form)
+ throws UniformInterfaceException, ClientHandlerException,
+ KustvaktException {
+ ClientResponse response = resource().path("oauth2").path("client")
+ .path("privilege")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ }
+
+ private JsonNode retrieveClientInfo (String clientId, String username)
+ throws UniformInterfaceException, ClientHandlerException,
+ KustvaktException {
+ ClientResponse response = resource().path("oauth2").path("client")
+ .path("info").path(clientId)
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .get(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ String entity = response.getEntity(String.class);
+ return JsonUtils.readTree(entity);
+ }
}
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index 3dabb7b..5d452e8 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -181,6 +181,46 @@
}
@Test
+ public void testRequestTokenAuthorizationPublic () throws KustvaktException {
+ String clientId = "8bIDtZnH6NvRkW2Fq";
+ MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
+ authForm.add("response_type", "code");
+ authForm.add("client_id", clientId);
+
+ ClientResponse response = requestAuthorization(authForm);
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ String code = params.get("code").get(0);
+
+ MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
+ tokenForm.add("grant_type", "authorization_code");
+ tokenForm.add("client_id", clientId);
+ tokenForm.add("code", code);
+
+ response = requestToken(tokenForm);
+ String entity = response.getEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+
+ String accessToken = node.at("/access_token").asText();
+ String refreshToken = node.at("/refresh_token").asText();
+
+ assertEquals(TokenType.BEARER.toString(),
+ node.at("/token_type").asText());
+ assertNotNull(node.at("/expires_in").asText());
+
+ testRevokeTokenPublicClient(accessToken, clientId, "access_token");
+
+ testRequestRefreshTokenInvalidScope(clientId, refreshToken);
+ testRequestRefreshTokenPublicClient(clientId, refreshToken);
+ testRequestRefreshTokenInvalidClient(refreshToken);
+ testRequestRefreshTokenInvalidRefreshToken(clientId);
+
+ testRevokeTokenPublicClient(refreshToken, clientId, "refresh_token");
+ testRequestRefreshWithRevokedRefreshToken(clientId, refreshToken);
+ }
+
+ @Test
public void testRequestTokenAuthorizationConfidential ()
throws KustvaktException {
@@ -203,7 +243,6 @@
tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
tokenForm.add("client_secret", "secret");
tokenForm.add("code", code);
- System.out.println(code);
response = requestToken(tokenForm);
String entity = response.getEntity(String.class);
@@ -437,7 +476,7 @@
@Test
public void testRequestTokenPasswordGrantPublic ()
throws KustvaktException {
- String clientId = "iBr3LsTCxOj7D2o0A5m";
+ String clientId = "8bIDtZnH6NvRkW2Fq";
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", "password");
form.add("username", "dory");
@@ -447,35 +486,23 @@
ClientResponse response = requestToken(form);
String entity = response.getEntity(String.class);
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
- String accessToken = node.at("/access_token").asText();
- assertEquals(TokenType.BEARER.toString(),
- node.at("/token_type").asText());
- assertNotNull(node.at("/expires_in").asText());
-
-
- testRevokeTokenPublicClient(accessToken, clientId, "access_token");
-
- String refreshToken = node.at("/refresh_token").asText();
- testRequestRefreshTokenInvalidScope(clientId, refreshToken);
- testRequestRefreshTokenPublicClient(clientId, refreshToken);
- testRequestRefreshTokenInvalidClient(refreshToken);
- testRequestRefreshTokenInvalidRefreshToken(clientId);
-
- testRevokeTokenPublicClient(refreshToken, clientId, "refresh_token");
- testRequestRefreshWithRevokedRefreshToken(clientId, refreshToken);
+ assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT,
+ node.at("/error").asText());
+ assertEquals("Password grant is not allowed for third party clients",
+ node.at("/error_description").asText());
}
@Test
- public void testRequestTokenPasswordGrantNonNative ()
+ public void testRequestTokenPasswordGrantConfidentialNonSuper ()
throws KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", "password");
form.add("username", "dory");
form.add("password", "password");
- // confidential nonnative
+ // confidential non-super
form.add("client_id", "9aHsGW6QflV13ixNpez");
form.add("client_secret", "secret");
@@ -639,7 +666,7 @@
throws KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", GrantType.REFRESH_TOKEN.toString());
- form.add("client_id", "8bIDtZnH6NvRkW2Fq");
+ form.add("client_id", "iBr3LsTCxOj7D2o0A5m");
form.add("refresh_token", refreshToken);
ClientResponse response = resource().path("oauth2").path("token")
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
index eb716ab..abb44d7 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2OpenIdControllerTest.java
@@ -103,8 +103,6 @@
assertEquals("thisIsMyState", params.getFirst("state"));
}
-
-
private void testRequestAuthorizationCodeWithoutOpenID (
MultivaluedMap<String, String> form, String redirectUri)
throws KustvaktException {
@@ -350,14 +348,13 @@
assertEquals(nonce, claimsSet.getClaim("nonce"));
}
-
// no openid
@Test
public void testRequestAccessTokenWithPassword ()
throws KustvaktException, ParseException, InvalidKeySpecException,
NoSuchAlgorithmException, JOSEException {
// public client
- String client_id = "iBr3LsTCxOj7D2o0A5m";
+ String client_id = "8bIDtZnH6NvRkW2Fq";
MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
testRequestAccessTokenMissingGrant(tokenForm);
@@ -373,13 +370,12 @@
ClientResponse tokenResponse = sendTokenRequest(tokenForm);
String entity = tokenResponse.getEntity(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());
- }
+ assertEquals(OAuth2Error.UNAUTHORIZED_CLIENT,
+ node.at("/error").asText());
+ assertEquals("Password grant is not allowed for third party clients",
+ node.at("/error_description").asText());
+ }
private void testRequestAccessTokenMissingUsername (
MultivaluedMap<String, String> tokenForm) throws KustvaktException {