Implemented degrading super clients, improved and added OAuth2 tests.

Change-Id: I1359149050dd3609dfdb4d72d1bc2ca11b95cded
diff --git a/full/Changes b/full/Changes
index 81f9b88..60bf81a 100644
--- a/full/Changes
+++ b/full/Changes
@@ -19,6 +19,9 @@
     - Marked native clients implementation to deprecated in favour of super clients (margaretha)
     - Enabled using Bearer tokens as user authentication tokens (Authorization header value) for many 
       controllers including OAuth2 controllers (margaretha)
+16/08/2018
+    - Implemented degrading super clients (margaretha)
+    - Improved and added OAuth2 tests (margaretha)    
 
 
 # version 0.60.5
diff --git a/full/src/main/java/de/ids_mannheim/korap/constant/TokenType.java b/full/src/main/java/de/ids_mannheim/korap/constant/TokenType.java
index 6d5a00b..418be0b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/constant/TokenType.java
+++ b/full/src/main/java/de/ids_mannheim/korap/constant/TokenType.java
@@ -1,5 +1,7 @@
 package de.ids_mannheim.korap.constant;
 
+import org.apache.commons.lang.StringUtils;
+
 public enum TokenType {
     BASIC, API, SESSION, 
     // openid token, e.g. within oauth2 response (json body)
@@ -10,6 +12,6 @@
     CLIENT; 
 
     public String displayName () {
-        return name().toLowerCase();
+        return StringUtils.capitalize(name().toLowerCase());
     }
 }
\ No newline at end of file
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
index 16ff581..1aba796 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
@@ -341,7 +341,6 @@
         String tokenType = revokeTokenRequest.getTokenType();
 
         clientService.authenticateClient(clientId, clientSecret);
-        tokenDao.removeCacheEntry(token);
         if (tokenType != null && tokenType.equals("refresh_token")) {
             if (!revokeRefreshToken(token)) {
                 revokeAccessToken(token);
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index cdd6a66..6e8803a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -183,23 +183,7 @@
                 || client.getRegisteredBy().equals(username)) {
 
             clientDao.deregisterClient(client);
-
-            // revoke all related authorization tokens
-            List<Authorization> authList =
-                    authorizationDao.retrieveAuthorizationsByClientId(clientId);
-            for (Authorization authorization : authList) {
-                authorization.setRevoked(true);
-                authorizationDao.updateAuthorization(authorization);
-            }
-
-            // revoke all related access tokens
-            List<AccessToken> tokens =
-                    tokenDao.retrieveAccessTokenByClientId(clientId);
-            for (AccessToken token : tokens) {
-                token.setRevoked(true);
-                token.setRefreshTokenRevoked(true);
-                tokenDao.updateAccessToken(token);
-            }
+            revokeAllAuthorizationsByClientId(clientId);
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
@@ -207,6 +191,27 @@
         }
     }
 
+    private void revokeAllAuthorizationsByClientId (String clientId)
+            throws KustvaktException {
+
+        // revoke all related authorization codes
+        List<Authorization> authList =
+                authorizationDao.retrieveAuthorizationsByClientId(clientId);
+        for (Authorization authorization : authList) {
+            authorization.setRevoked(true);
+            authorizationDao.updateAuthorization(authorization);
+        }
+
+        // revoke all related access tokens
+        List<AccessToken> tokens =
+                tokenDao.retrieveAccessTokenByClientId(clientId);
+        for (AccessToken token : tokens) {
+            token.setRevoked(true);
+            token.setRefreshTokenRevoked(true);
+            tokenDao.updateAccessToken(token);
+        }
+    }
+
     public OAuth2ClientDto resetSecret (String clientId, String clientSecret,
             String username) throws KustvaktException {
 
@@ -258,8 +263,8 @@
                         OAuth2Error.INVALID_REQUEST);
             }
         }
-        else if (client.getSecret() == null || client.getSecret().isEmpty()){
-            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)){
+        else if (client.getSecret() == null || client.getSecret().isEmpty()) {
+            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
                 throw new KustvaktException(
                         StatusCodes.CLIENT_AUTHENTICATION_FAILED,
                         "Client secret was not registered",
@@ -291,11 +296,16 @@
 
         if (adminDao.isAdmin(username)) {
             OAuth2Client client = clientDao.retrieveClientById(clientId);
-            if (isSuper && !client.getType()
-                    .equals(OAuth2ClientType.CONFIDENTIAL)) {
-                throw new KustvaktException(StatusCodes.NOT_ALLOWED,
-                        "Only confidential clients are allowed to be super clients.");
+            if (isSuper) {
+                if (!client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+                    throw new KustvaktException(StatusCodes.NOT_ALLOWED,
+                            "Only confidential clients are allowed to be super clients.");
+                }
             }
+            else {
+                revokeAllAuthorizationsByClientId(clientId);
+            }
+
             client.setSuper(isSuper);
             clientDao.updateClient(client);
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index 91a129d..08889cd 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -172,10 +172,17 @@
      * setting a specific client to be a super client.
      * Only confidential clients are allowed to be super clients.
      * 
+     * When upgrading clients to super clients, existing access tokens
+     * and authorization codes retain their scopes.
+     * 
+     * When degrading super clients, all existing tokens and
+     * authorization codes are invalidated.
+     * 
      * @param securityContext
-     * @param clientId
-     * @param super true indicating super client, false otherwise
-     * @return Response status OK, if successful 
+     * @param clientId OAuth2 client id
+     * @param super
+     *            true indicating super client, false otherwise
+     * @return Response status OK, if successful
      */
     @POST
     @Path("privilege")
@@ -189,8 +196,7 @@
         try {
             scopeService.verifyScope(context, OAuth2Scope.ADMIN);
             clientService.updatePrivilege(context.getUsername(), clientId,
-                    true);
-//                    Boolean.valueOf(isSuper));
+                    Boolean.valueOf(isSuper));
             return Response.ok().build();
         }
         catch (KustvaktException e) {
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 78d91aa..93cf3c2 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
@@ -5,17 +5,13 @@
 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;
@@ -24,102 +20,29 @@
 
 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.constant.TokenType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
-public class OAuth2AccessTokenTest extends SpringJerseyTest {
+public class OAuth2AccessTokenTest extends OAuth2TestBase {
 
-    // normal client
-    private String clientId = "9aHsGW6QflV13ixNpez";
-    private String superClientId = "fCBbQkAyYzI4NzUxMg";
-    private String clientSecret = "secret";
+    private String userAuthHeader;
+    private String clientAuthHeader;
 
-    private String requestAuthorizationCode (String scope, String authHeader)
-            throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("response_type", "code");
-        form.add("client_id", clientId);
-        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
+    public OAuth2AccessTokenTest () throws KustvaktException {
+        userAuthHeader = 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,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).post(ClientResponse.class);
-
-        String entity = response.getEntity(String.class);
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        JsonNode node = JsonUtils.readTree(entity);
-        return node;
-    }
-
-    // client credentials in authorization header
-    private JsonNode requestTokenWithAuthorizationHeader (String code)
-            throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "authorization_code");
-        form.add("client_id", clientId);
-        form.add("code", code);
-
-        ClientResponse response = resource().path("oauth2").path("token")
-                .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);
-        return JsonUtils.readTree(entity);
+        clientAuthHeader =
+                HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(
+                        confidentialClientId, clientSecret);
     }
 
     @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);
+        ClientResponse response =
+                requestTokenWithPassword(superClientId, clientSecret);
 
         JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
         assertEquals("all", node.at("/scope").asText());
@@ -136,18 +59,19 @@
     }
 
     @Test
-    public void testCustomAuthorizationScope () throws KustvaktException {
-        String authHeader = HttpAuthorizationHandler
-                .createBasicAuthorizationHeaderValue("dory", "password");
-        String code = requestAuthorizationCode(OAuth2Scope.VC_INFO.toString(),
-                authHeader);
-        JsonNode node = requestTokenWithAuthorizationHeader(code);
+    public void testCustomScope () throws KustvaktException {
+        String code = requestAuthorizationCode(confidentialClientId,
+                clientSecret, OAuth2Scope.VC_INFO.toString(), userAuthHeader);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, code);
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
 
         String token = node.at("/access_token").asText();
         assertTrue(node.at("/scope").asText()
                 .contains(OAuth2Scope.VC_INFO.toString()));
 
-        ClientResponse response = resource().path("vc").path("list")
+        // test list vc using the token
+        response = resource().path("vc").path("list")
                 .header(Attributes.AUTHORIZATION, "Bearer " + token)
                 .get(ClientResponse.class);
 
@@ -158,14 +82,20 @@
 
     @Test
     public void testDefaultScope () throws KustvaktException, IOException {
-        String accessToken = requestTokenWithAuthorizationCodeGrant()
-                .at("/access_token").asText();
-        testListVCScopeNotAuthorized(accessToken);
-        testListVCAccessBearerNotAuthorize(accessToken);
+        String code = requestAuthorizationCode(confidentialClientId, clientSecret,
+                null, userAuthHeader);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, code);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        String accessToken = node.at("/access_token").asText();
+        testScopeNotAuthorized(accessToken);
+        testScopeNotAuthorize2(accessToken);
         testSearchWithOAuth2Token(accessToken);
     }
 
-    private void testListVCScopeNotAuthorized (String accessToken)
+    private void testScopeNotAuthorized (String accessToken)
             throws KustvaktException {
         ClientResponse response = resource().path("vc").path("list")
                 .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
@@ -181,7 +111,7 @@
                 node.at("/errors/0/1").asText());
     }
 
-    private void testListVCAccessBearerNotAuthorize (String accessToken)
+    private void testScopeNotAuthorize2 (String accessToken)
             throws KustvaktException {
         ClientResponse response =
                 resource().path("vc").path("access").path("list")
@@ -200,18 +130,11 @@
 
     private void testSearchWithOAuth2Token (String accessToken)
             throws KustvaktException, IOException {
-        ClientResponse response = resource().path("search")
-                .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
-                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .get(ClientResponse.class);
-
-        String ent = response.getEntity(String.class);
-
+        ClientResponse response = searchWithAccessToken(accessToken);
+        String entity = response.getEntity(String.class);
         assertEquals(ClientResponse.Status.OK.getStatusCode(),
                 response.getStatus());
-
-        JsonNode node = JsonUtils.readTree(ent);
+        JsonNode node = JsonUtils.readTree(entity);
         assertNotNull(node);
         assertEquals(25, node.at("/matches").size());
     }
@@ -219,11 +142,8 @@
     @Test
     public void testSearchWithUnknownToken ()
             throws KustvaktException, IOException {
-        ClientResponse response = resource().path("search")
-                .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
-                .header(Attributes.AUTHORIZATION,
-                        "Bearer ljsa8tKNRSczJhk20öhq92zG8z350")
-                .get(ClientResponse.class);
+        ClientResponse response =
+                searchWithAccessToken("ljsa8tKNRSczJhk20öhq92zG8z350");
 
         assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
                 response.getStatus());
@@ -239,11 +159,15 @@
     @Test
     public void testRevokeAccessTokenConfidentialClient ()
             throws KustvaktException {
-        String accessToken = requestTokenWithAuthorizationCodeGrant()
-                .at("/access_token").asText();
+        String code = requestAuthorizationCode(confidentialClientId,
+                clientSecret, null, userAuthHeader);
+        JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+                confidentialClientId, code, clientAuthHeader);
+
+        String accessToken = node.at("/access_token").asText();
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("token", accessToken);
-        form.add("client_id", clientId);
+        form.add("client_id", confidentialClientId);
         form.add("client_secret", "secret");
 
         ClientResponse response = resource().path("oauth2").path("revoke")
@@ -253,17 +177,12 @@
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
-        testSearchWithRevokedToken(accessToken);
+        testSearchWithRevokedAccessToken(accessToken);
     }
 
-    private void testSearchWithRevokedToken (String accessToken)
+    private void testSearchWithRevokedAccessToken (String accessToken)
             throws KustvaktException {
-        ClientResponse response = resource().path("search")
-                .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
-                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .get(ClientResponse.class);
-
+        ClientResponse response = searchWithAccessToken(accessToken);
         String entity = response.getEntity(String.class);
         assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
                 response.getStatus());
@@ -273,19 +192,22 @@
                 node.at("/errors/0/0").asInt());
         assertEquals("Access token has been revoked",
                 node.at("/errors/0/1").asText());
-
     }
 
     @Test
-    public void testRevocationAfterRequestRefreshToken ()
+    public void testRevokedAccessTokenAfterRequestRefreshToken ()
             throws KustvaktException {
-        JsonNode node = requestTokenWithAuthorizationCodeGrant();
+        String code = requestAuthorizationCode(confidentialClientId,
+                clientSecret, null, userAuthHeader);
+        JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+                confidentialClientId, code, clientAuthHeader);
+
         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);
+        form.add("client_id", confidentialClientId);
         form.add("client_secret", "secret");
         form.add("refresh_token", refreshToken);
 
@@ -302,17 +224,21 @@
         assertNotNull(node.at("/access_token").asText());
         assertEquals(refreshToken, node.at("/refresh_token").asText());
 
-        testSearchWithRevokedToken(accessToken);
+        testSearchWithRevokedAccessToken(accessToken);
     }
 
     @Test
-    public void testRequestAuthorizationWithBearerTokenUnauthorized () throws KustvaktException {
-        String userAuthToken = requestTokenWithAuthorizationCodeGrant()
-                .at("/access_token").asText();
+    public void testRequestAuthorizationWithBearerTokenUnauthorized ()
+            throws KustvaktException {
+        String code = requestAuthorizationCode(confidentialClientId,
+                clientSecret, null, userAuthHeader);
+        JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
+                confidentialClientId, code, clientAuthHeader);
+        String userAuthToken = node.at("/access_token").asText();
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
-        form.add("client_id", clientId);
+        form.add("client_id", confidentialClientId);
         form.add("client_secret", clientSecret);
 
         ClientResponse response = resource().path("oauth2").path("authorize")
@@ -323,8 +249,8 @@
                 .entity(form).post(ClientResponse.class);
 
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-        
-        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+
+        node = JsonUtils.readTree(response.getEntity(String.class));
         assertEquals(StatusCodes.AUTHORIZATION_FAILED,
                 node.at("/errors/0/0").asInt());
         assertEquals("Scope authorize is not authorized",
@@ -334,31 +260,20 @@
     @Test
     public void testRequestAuthorizationWithBearerToken ()
             throws KustvaktException {
-        String userAuthToken = requestTokenWithPasswordGrant();
-        String code = requestAuthorizationCode(null, "Bearer " + userAuthToken);
+        ClientResponse response =
+                requestTokenWithPassword(superClientId, clientSecret);
+        String entity = response.getEntity(String.class);
+        
+        JsonNode node = JsonUtils.readTree(entity);
+        String userAuthToken = node.at("/access_token").asText();
+        assertNotNull(userAuthToken);
+        assertEquals(TokenType.BEARER.displayName(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+
+        String code = requestAuthorizationCode(superClientId,
+                clientSecret, 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 9c3d536..aa32cb3 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
@@ -4,7 +4,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import java.net.URI;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -13,8 +12,6 @@
 
 import org.apache.http.entity.ContentType;
 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;
@@ -27,7 +24,6 @@
 
 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;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
@@ -39,9 +35,15 @@
  * @author margaretha
  *
  */
-public class OAuth2ClientControllerTest extends SpringJerseyTest {
+public class OAuth2ClientControllerTest extends OAuth2TestBase {
 
     private String username = "OAuth2ClientControllerTest";
+    private String userAuthHeader;
+
+    public OAuth2ClientControllerTest () throws KustvaktException {
+        userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("dory", "password");
+    }
 
     private void checkWWWAuthenticateHeader (ClientResponse response) {
         Set<Entry<String, List<String>>> headers =
@@ -73,6 +75,21 @@
                 .entity(json).post(ClientResponse.class);
     }
 
+    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);
+    }
+
     @Test
     public void testRegisterConfidentialClient () throws KustvaktException {
         ClientResponse response = registerConfidentialClient();
@@ -127,7 +144,7 @@
         assertTrue(node.at("/client_secret").isMissingNode());
 
         testResetPublicClientSecret(clientId);
-        testAccessTokenAfterDeregistration(clientId);
+        testAccessTokenAfterDeregistration(clientId, null);
     }
 
     @Test
@@ -158,20 +175,26 @@
         testDeregisterPublicClient(clientId);
     }
 
-    private void testAccessTokenAfterDeregistration (String clientId)
-            throws KustvaktException {
-        String code = requestAuthorizationCode(clientId, "");
-        ClientResponse response = requestAccessToken(code, clientId, "");
+    private void testAccessTokenAfterDeregistration (String clientId,
+            String clientSecret) throws KustvaktException {
+        String userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("dory", "password");
+
+        String code =
+                requestAuthorizationCode(clientId, "", null, userAuthHeader);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(clientId,
+                clientSecret, code);
         JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
         String accessToken = node.at("/access_token").asText();
 
         response = searchWithAccessToken(accessToken);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
-        code = requestAuthorizationCode(clientId, "");
+        code = requestAuthorizationCode(clientId, "", null, userAuthHeader);
         testDeregisterPublicClient(clientId);
 
-        response = requestAccessToken(code, clientId, "");
+        response = requestTokenWithAuthorizationCodeAndForm(clientId,
+                clientSecret, code);
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
         node = JsonUtils.readTree(response.getEntity(String.class));
         assertEquals(OAuth2Error.INVALID_CLIENT.toString(),
@@ -184,59 +207,6 @@
                 node.at("/errors/0/0").asInt());
         assertEquals("Access token has been revoked",
                 node.at("/errors/0/1").asText());
-
-    }
-
-    private String requestAuthorizationCode (String clientId,
-            String clientSecret) throws UniformInterfaceException,
-            ClientHandlerException, KustvaktException {
-        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,
-                        HttpAuthorizationHandler
-                                .createBasicAuthorizationHeaderValue("dory",
-                                        "password"))
-                .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");
-    }
-
-    private ClientResponse requestAccessToken (String code, String clientId,
-            String clientSecret) throws KustvaktException {
-        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,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).post(ClientResponse.class);
-
-        return response;
-    }
-
-    private ClientResponse searchWithAccessToken (String accessToken) {
-        ClientResponse response = resource().path("search")
-                .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
-                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .get(ClientResponse.class);
-
-        return response;
     }
 
     private void testDeregisterPublicClientMissingUserAuthentication (
@@ -395,52 +365,74 @@
 
     @Test
     public void testUpdateClientPrivilege () throws KustvaktException {
+        // register a client
         ClientResponse response = registerConfidentialClient();
         JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
         String clientId = node.at("/client_id").asText();
+        String clientSecret = node.at("/client_secret").asText();
 
+        // request an access token
+        String clientAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue(clientId, clientSecret);
+        String code = requestAuthorizationCode(clientId, clientSecret, null,
+                userAuthHeader);
+        node = requestTokenWithAuthorizationCodeAndHeader(clientId, code,
+                clientAuthHeader);
+        String accessToken = node.at("/access_token").asText();
+
+        testAccessTokenAfterUpgradingClient(clientId, accessToken);
+        testAccessTokenAfterDegradingSuperClient(clientId, accessToken);
+    }
+
+    // old access tokens retain their scopes
+    private void testAccessTokenAfterUpgradingClient (String clientId,
+            String accessToken) throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("client_id", clientId);
         form.add("super", "true");
 
         updateClientPrivilege(form);
-        node = retrieveClientInfo(clientId, "admin");
+        JsonNode 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"))
+        // list vc
+        ClientResponse response = resource().path("vc").path("list")
+                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
                 .get(ClientResponse.class);
 
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
+        assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
+                response.getStatus());
         String entity = response.getEntity(String.class);
-        return JsonUtils.readTree(entity);
+        node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals("Scope vc_info is not authorized",
+                node.at("/errors/0/1").asText());
+        
+        // search
+        response = searchWithAccessToken(accessToken);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    private void testAccessTokenAfterDegradingSuperClient (String clientId,
+            String accessToken) throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+        form.add("super", "false");
+        
+        updateClientPrivilege(form);
+        JsonNode node = retrieveClientInfo(clientId, username);
+        assertTrue(node.at("/isSuper").isMissingNode());
+
+        ClientResponse response = searchWithAccessToken(accessToken);
+        assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
+                response.getStatus());
+        
+        String entity = response.getEntity(String.class);
+        node = JsonUtils.readTree(entity);
+        assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+                node.at("/errors/0/0").asInt());
+        assertEquals("Access token has been revoked",
+                node.at("/errors/0/1").asText());
     }
 }
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 5d452e8..d5c7140 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
@@ -23,8 +23,6 @@
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 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;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.utils.JsonUtils;
@@ -33,29 +31,24 @@
  * @author margaretha
  *
  */
-public class OAuth2ControllerTest extends SpringJerseyTest {
+public class OAuth2ControllerTest extends OAuth2TestBase {
 
-    private ClientResponse requestAuthorization (
-            MultivaluedMap<String, String> form) throws KustvaktException {
+    public String userAuthHeader;
 
-        return resource().path("oauth2").path("authorize").header(
-                Attributes.AUTHORIZATION,
-                HttpAuthorizationHandler.createBasicAuthorizationHeaderValue(
-                        "dory", "password"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).post(ClientResponse.class);
+    public OAuth2ControllerTest () throws KustvaktException {
+        userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("dory", "password");
     }
 
     @Test
     public void testAuthorizeConfidentialClient () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", confidentialClientId);
         form.add("state", "thisIsMyState");
 
-        ClientResponse response = requestAuthorization(form);
+        ClientResponse response =
+                requestAuthorizationCode(form, userAuthHeader);
 
         assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
                 response.getStatus());
@@ -68,20 +61,9 @@
 
     @Test
     public void testAuthorizePublicClient () throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("response_type", "code");
-        form.add("client_id", "8bIDtZnH6NvRkW2Fq");
-        form.add("state", "thisIsMyState");
-
-        ClientResponse response = requestAuthorization(form);
-
-        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
-                response.getStatus());
-        URI redirectUri = response.getLocation();
-        MultiValueMap<String, String> params = UriComponentsBuilder
-                .fromUri(redirectUri).build().getQueryParams();
-        assertNotNull(params.getFirst("code"));
-        assertEquals("thisIsMyState", params.getFirst("state"));
+        String code = requestAuthorizationCode(publicClientId, clientSecret,
+                null, userAuthHeader);
+        assertNotNull(code);
     }
 
     @Test
@@ -90,10 +72,11 @@
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", confidentialClientId);
         form.add("redirect_uri", redirectUri);
         form.add("state", "thisIsMyState");
-        ClientResponse response = requestAuthorization(form);
+        ClientResponse response =
+                requestAuthorizationCode(form, userAuthHeader);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -112,7 +95,8 @@
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("state", "thisIsMyState");
         // missing response_type
-        ClientResponse response = requestAuthorization(form);
+        ClientResponse response =
+                requestAuthorizationCode(form, userAuthHeader);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -126,7 +110,7 @@
 
         // missing client_id
         form.add("response_type", "code");
-        response = requestAuthorization(form);
+        response = requestAuthorizationCode(form, userAuthHeader);
         entity = response.getEntity(String.class);
         node = JsonUtils.readTree(entity);
         assertEquals("Missing parameters: client_id",
@@ -139,7 +123,8 @@
         form.add("response_type", "string");
         form.add("state", "thisIsMyState");
 
-        ClientResponse response = requestAuthorization(form);
+        ClientResponse response =
+                requestAuthorizationCode(form, userAuthHeader);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         String entity = response.getEntity(String.class);
@@ -155,11 +140,12 @@
     public void testAuthorizeInvalidScope () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("response_type", "code");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", confidentialClientId);
         form.add("scope", "read_address");
         form.add("state", "thisIsMyState");
 
-        ClientResponse response = requestAuthorization(form);
+        ClientResponse response =
+                requestAuthorizationCode(form, userAuthHeader);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         URI location = response.getLocation();
@@ -171,65 +157,48 @@
         assertEquals("thisIsMyState", params.getFirst("state"));
     }
 
-    private ClientResponse requestToken (MultivaluedMap<String, String> form)
-            throws KustvaktException {
-        return resource().path("oauth2").path("token")
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).post(ClientResponse.class);
-    }
-
     @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);
+    public void testRequestTokenAuthorizationPublic ()
+            throws KustvaktException {
+        String code = requestAuthorizationCode(publicClientId, "", null,
+                userAuthHeader);
 
-        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);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                publicClientId, clientSecret, code);
         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);
+
+        testRevokeTokenPublicClient(accessToken, publicClientId,
+                "access_token");
+
+        testRequestRefreshTokenInvalidScope(publicClientId, refreshToken);
+        testRequestRefreshTokenPublicClient(publicClientId, refreshToken);
         testRequestRefreshTokenInvalidClient(refreshToken);
-        testRequestRefreshTokenInvalidRefreshToken(clientId);
-        
-        testRevokeTokenPublicClient(refreshToken, clientId, "refresh_token");
-        testRequestRefreshWithRevokedRefreshToken(clientId, refreshToken);
+        testRequestRefreshTokenInvalidRefreshToken(publicClientId);
+
+        testRevokeTokenPublicClient(refreshToken, publicClientId,
+                "refresh_token");
+        testRequestRefreshWithRevokedRefreshToken(publicClientId, refreshToken);
     }
-    
+
     @Test
     public void testRequestTokenAuthorizationConfidential ()
             throws KustvaktException {
 
         MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
         authForm.add("response_type", "code");
-        authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        authForm.add("client_id", confidentialClientId);
         authForm.add("scope", "search");
 
-        ClientResponse response = requestAuthorization(authForm);
+        ClientResponse response =
+                requestAuthorizationCode(authForm, userAuthHeader);
         URI redirectUri = response.getLocation();
         MultivaluedMap<String, String> params =
                 UriComponent.decodeQuery(redirectUri, true);
@@ -238,13 +207,8 @@
 
         assertEquals(scopes, "search");
 
-        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
-        tokenForm.add("grant_type", "authorization_code");
-        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
-        tokenForm.add("client_secret", "secret");
-        tokenForm.add("code", code);
-
-        response = requestToken(tokenForm);
+        response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, code);
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
         assertNotNull(node.at("/access_token").asText());
@@ -253,12 +217,13 @@
                 node.at("/token_type").asText());
         assertNotNull(node.at("/expires_in").asText());
 
-        testRequestTokenWithUsedAuthorization(tokenForm);
+        testRequestTokenWithUsedAuthorization(code);
     }
 
-    private void testRequestTokenWithUsedAuthorization (
-            MultivaluedMap<String, String> form) throws KustvaktException {
-        ClientResponse response = requestToken(form);
+    private void testRequestTokenWithUsedAuthorization (String code)
+            throws KustvaktException {
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, code);
         String entity = response.getEntity(String.class);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -273,13 +238,8 @@
     @Test
     public void testRequestTokenInvalidAuthorizationCode ()
             throws KustvaktException {
-        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
-        tokenForm.add("grant_type", "authorization_code");
-        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
-        tokenForm.add("client_secret", "secret");
-        tokenForm.add("code", "blahblah");
-
-        ClientResponse response = requestToken(tokenForm);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, "blahblah");
         String entity = response.getEntity(String.class);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
@@ -292,14 +252,15 @@
     @Test
     public void testRequestTokenAuthorizationReplyAttack ()
             throws KustvaktException {
-        String uri = "https://korap.ids-mannheim.de/confidential/redirect";
+        String uri = "https://third.party.com/confidential/redirect";
         MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
         authForm.add("response_type", "code");
-        authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        authForm.add("client_id", confidentialClientId);
         authForm.add("scope", "search");
         authForm.add("redirect_uri", uri);
 
-        ClientResponse response = requestAuthorization(authForm);
+        ClientResponse response =
+                requestAuthorizationCode(authForm, userAuthHeader);
         URI redirectUri = response.getLocation();
         MultivaluedMap<String, String> params =
                 UriComponent.decodeQuery(redirectUri, true);
@@ -312,13 +273,8 @@
 
     private void testRequestTokenAuthorizationInvalidClient (String code)
             throws KustvaktException {
-        MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
-        tokenForm.add("grant_type", "authorization_code");
-        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
-        tokenForm.add("client_secret", "blah");
-        tokenForm.add("code", code);
-
-        ClientResponse response = requestToken(tokenForm);
+        ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, "wrong_secret", code);
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(OAuth2Error.INVALID_CLIENT, node.at("/error").asText());
@@ -328,7 +284,7 @@
             throws KustvaktException {
         MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
         tokenForm.add("grant_type", "authorization_code");
-        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_id", confidentialClientId);
         tokenForm.add("client_secret", "secret");
         tokenForm.add("code", code);
         tokenForm.add("redirect_uri", "https://blahblah.com");
@@ -343,7 +299,7 @@
             throws KustvaktException {
         MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
         tokenForm.add("grant_type", "authorization_code");
-        tokenForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        tokenForm.add("client_id", confidentialClientId);
         tokenForm.add("client_secret", "secret");
         tokenForm.add("code", code);
         tokenForm.add("redirect_uri", uri);
@@ -357,20 +313,15 @@
                 node.at("/error_description").asText());
     }
 
-
     @Test
-    public void testRequestTokenPasswordGrantConfidential ()
+    public void testRequestTokenPasswordGrantConfidentialSuper ()
             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");
+        ClientResponse response =
+                requestTokenWithPassword(superClientId, clientSecret);
 
-        ClientResponse response = requestToken(form);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
         String entity = response.getEntity(String.class);
-
         JsonNode node = JsonUtils.readTree(entity);
         assertNotNull(node.at("/access_token").asText());
         assertEquals(TokenType.BEARER.toString(),
@@ -379,11 +330,41 @@
     }
 
     @Test
+    public void testRequestTokenPasswordGrantConfidentialNonSuper ()
+            throws KustvaktException {
+        ClientResponse response =
+                requestTokenWithPassword(confidentialClientId, clientSecret);
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT,
+                node.at("/error").asText());
+        assertEquals("Password grant is not allowed for third party clients",
+                node.at("/error_description").asText());
+    }
+
+    @Test
+    public void testRequestTokenPasswordGrantPublic ()
+            throws KustvaktException {
+        ClientResponse response = requestTokenWithPassword(publicClientId, "");
+        String entity = response.getEntity(String.class);
+
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        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 testRequestTokenPasswordGrantAuthorizationHeader ()
             throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "password");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", superClientId);
         form.add("username", "dory");
         form.add("password", "password");
 
@@ -435,14 +416,8 @@
     @Test
     public void testRequestTokenPasswordGrantMissingClientSecret ()
             throws KustvaktException {
-
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "password");
-        form.add("username", "dory");
-        form.add("password", "password");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
-
-        ClientResponse response = requestToken(form);
+        ClientResponse response =
+                requestTokenWithPassword(confidentialClientId, "");
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
         String entity = response.getEntity(String.class);
@@ -456,13 +431,7 @@
     @Test
     public void testRequestTokenPasswordGrantMissingClientId ()
             throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "password");
-        form.add("username", "dory");
-        form.add("password", "password");
-        form.add("client_secret", "secret");
-
-        ClientResponse response = requestToken(form);
+        ClientResponse response = requestTokenWithPassword(null, clientSecret);
         String entity = response.getEntity(String.class);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
 
@@ -474,56 +443,12 @@
     }
 
     @Test
-    public void testRequestTokenPasswordGrantPublic ()
-            throws KustvaktException {
-        String clientId = "8bIDtZnH6NvRkW2Fq";
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "password");
-        form.add("username", "dory");
-        form.add("password", "password");
-        form.add("client_id", clientId);
-
-        ClientResponse response = requestToken(form);
-        String entity = response.getEntity(String.class);
-
-        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-
-        JsonNode node = JsonUtils.readTree(entity);
-        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 testRequestTokenPasswordGrantConfidentialNonSuper ()
-            throws KustvaktException {
-        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("grant_type", "password");
-        form.add("username", "dory");
-        form.add("password", "password");
-        // confidential non-super
-        form.add("client_id", "9aHsGW6QflV13ixNpez");
-        form.add("client_secret", "secret");
-
-        ClientResponse response = requestToken(form);
-        String entity = response.getEntity(String.class);
-        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-
-        JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT,
-                node.at("/error").asText());
-        assertEquals("Password grant is not allowed for third party clients",
-                node.at("/error_description").asText());
-    }
-
-    @Test
     public void testRequestTokenClientCredentialsGrant ()
             throws KustvaktException {
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "client_credentials");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", confidentialClientId);
         form.add("client_secret", "secret");
         ClientResponse response = requestToken(form);
         String entity = response.getEntity(String.class);
@@ -548,7 +473,7 @@
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "client_credentials");
-        form.add("client_id", "8bIDtZnH6NvRkW2Fq");
+        form.add("client_id", publicClientId);
         form.add("client_secret", "");
         ClientResponse response = requestToken(form);
 
@@ -567,7 +492,7 @@
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("grant_type", "client_credentials");
-        form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+        form.add("client_id", confidentialClientId);
         form.add("client_secret", "secret");
         form.add("scope", "preferred_username client_info");
 
@@ -716,8 +641,8 @@
         assertEquals(OAuth2Error.INVALID_GRANT, node.at("/error").asText());
     }
 
-    private void testRevokeTokenPublicClient (String token,
-            String clientId, String tokenType) {
+    private void testRevokeTokenPublicClient (String token, String clientId,
+            String tokenType) {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("token_type", tokenType);
         form.add("token", token);
@@ -727,7 +652,7 @@
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
-        
+
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
 
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
new file mode 100644
index 0000000..fb85f17
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -0,0 +1,152 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+
+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.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.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+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;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/** Provides common methods and variables for OAuth2 tests,
+ *  and does not run any test. 
+ * 
+ * @author margaretha
+ *
+ */
+public class OAuth2TestBase extends SpringJerseyTest {
+
+    protected String publicClientId = "8bIDtZnH6NvRkW2Fq";
+    protected String confidentialClientId = "9aHsGW6QflV13ixNpez";
+    protected String superClientId = "fCBbQkAyYzI4NzUxMg";
+    protected String clientSecret = "secret";
+
+    public ClientResponse requestAuthorizationCode (
+            MultivaluedMap<String, String> form, String authHeader)
+            throws KustvaktException {
+
+        return 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);
+    }
+
+    public String requestAuthorizationCode (String clientId,
+            String clientSecret, String scope, String authHeader)
+            throws KustvaktException {
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", clientId);
+        form.add("client_secret", clientSecret);
+        if (scope != null) {
+            form.add("scope", scope);
+        }
+
+        ClientResponse response = requestAuthorizationCode(form, authHeader);
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+        URI redirectUri = response.getLocation();
+
+        MultiValueMap<String, String> params = UriComponentsBuilder
+                .fromUri(redirectUri).build().getQueryParams();
+        return params.getFirst("code");
+    }
+
+    public ClientResponse requestToken (MultivaluedMap<String, String> form)
+            throws KustvaktException {
+        return resource().path("oauth2").path("token")
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+    }
+
+    // client credentials as form params
+    public ClientResponse requestTokenWithAuthorizationCodeAndForm (
+            String clientId, String clientSecret, String code)
+            throws KustvaktException {
+
+        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);
+
+        return resource().path("oauth2").path("token")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+    }
+
+    // client credentials in authorization header
+    public JsonNode requestTokenWithAuthorizationCodeAndHeader (String clientId,
+            String code, String authHeader) throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "authorization_code");
+        form.add("client_id", clientId);
+        form.add("code", code);
+
+        ClientResponse response = resource().path("oauth2").path("token")
+                .header(Attributes.AUTHORIZATION, authHeader)
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        return JsonUtils.readTree(entity);
+    }
+
+    public ClientResponse requestTokenWithPassword (String clientId,
+            String clientSecret) throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "password");
+        form.add("client_id", clientId);
+        form.add("client_secret", clientSecret);
+        form.add("username", "dory");
+        form.add("password", "password");
+
+        return requestToken(form);
+    }
+
+    public 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());
+    }
+    
+    public ClientResponse searchWithAccessToken (String accessToken) {
+        return resource().path("search").queryParam("q", "Wasser")
+                .queryParam("ql", "poliqarp")
+                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+    }
+
+}