Updated and moved updateClientPrivilege API to OAuth2AdminController

Change-Id: I43dbf4dd2fd867cbdc91544d87333ab484bb48f7
diff --git a/full/Changes b/full/Changes
index 8b2f84e..6d9b013 100644
--- a/full/Changes
+++ b/full/Changes
@@ -13,6 +13,8 @@
  - Updated admin filter (admintoken as a form param) and uses 
    it for the closing index reader API. 
  - Removed unused admin API: clear access token cache.
+2022-03-29
+ - Updated and moved admin API: updateClientPrivilege to OAuth2AdminController
 
 # version 0.65.1
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java
index d5861d6..60edf29 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java
@@ -4,20 +4,47 @@
 import org.springframework.stereotype.Service;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
 import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 
 @Service
 public class OAuth2AdminService {
 
     @Autowired
+    private OAuth2ClientService clientService;
+
+    @Autowired
     private AccessTokenDao tokenDao;
     @Autowired
     private RefreshTokenDao refreshDao;
+    @Autowired
+    private OAuth2ClientDao clientDao;
 
     public void cleanTokens () {
         tokenDao.deleteInvalidAccessTokens();
         refreshDao.deleteInvalidRefreshTokens();
         tokenDao.clearCache();
     }
+
+    public void updatePrivilege (String clientId, boolean isSuper)
+            throws KustvaktException {
+
+        OAuth2Client client = clientDao.retrieveClientById(clientId);
+        if (isSuper) {
+            if (!client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+                throw new KustvaktException(StatusCodes.NOT_ALLOWED,
+                        "Only confidential clients are allowed to be super clients.");
+            }
+        }
+        else {
+            clientService.revokeAllAuthorizationsByClientId(clientId);
+        }
+
+        client.setSuper(isSuper);
+        clientDao.updateClient(client);
+    }
 }
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 51f8022..505140c 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
@@ -210,7 +210,7 @@
         }
     }
 
-    private void revokeAllAuthorizationsByClientId (String clientId)
+    public void revokeAllAuthorizationsByClientId (String clientId)
             throws KustvaktException {
 
         // revoke all related authorization codes
@@ -314,30 +314,6 @@
         return clientDao.retrieveClientById(clientId);
     }
 
-    public void updatePrivilege (String username, String clientId,
-            boolean isSuper) throws KustvaktException {
-
-        if (adminDao.isAdmin(username)) {
-            OAuth2Client client = clientDao.retrieveClientById(clientId);
-            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);
-        }
-        else {
-            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
-                    "Unauthorized operation for user: " + username, username);
-        }
-    }
-
     public OAuth2ClientInfoDto retrieveClientInfo (String username,
             String clientId) throws KustvaktException {
         OAuth2Client client = clientDao.retrieveClientById(clientId);
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
index 1d1ce6a..7249312 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
@@ -1,7 +1,11 @@
 package de.ids_mannheim.korap.web.controller;
 
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.SecurityContext;
 
@@ -48,4 +52,41 @@
         }
         return Response.ok().build();
     }
+
+    /**
+     * Facilitates editing client privileges for admin purposes, e.g.
+     * 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
+     *            OAuth2 client id
+     * @param super
+     *            true indicating super client, false otherwise
+     * @return Response status OK, if successful
+     */
+    @POST
+    @Path("client/privilege")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response updateClientPrivilege (
+            @Context SecurityContext securityContext,
+            @FormParam("client_id") String clientId,
+            @FormParam("super") String isSuper) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            scopeService.verifyScope(context, OAuth2Scope.ADMIN);
+            adminService.updatePrivilege(clientId, Boolean.valueOf(isSuper));
+            return Response.ok("SUCCESS").build();
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
 }
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 16f8bbb..05208f3 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
@@ -164,43 +164,6 @@
         }
     }
 
-    /**
-     * Facilitates editing client privileges for admin purposes, e.g.
-     * 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
-     *            OAuth2 client id
-     * @param super
-     *            true indicating super client, false otherwise
-     * @return Response status OK, if successful
-     */
-    @POST
-    @Path("privilege")
-    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response updateClientPrivilege (
-            @Context SecurityContext securityContext,
-            @FormParam("client_id") String clientId,
-            @FormParam("super") String isSuper) {
-        TokenContext context =
-                (TokenContext) securityContext.getUserPrincipal();
-        try {
-            scopeService.verifyScope(context, OAuth2Scope.ADMIN);
-            clientService.updatePrivilege(context.getUsername(), clientId,
-                    Boolean.valueOf(isSuper));
-            return Response.ok("SUCCESS").build();
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
-    }
 
     @GET
     @Path("{client_id}")
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
index 24445cd..435bf1b 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
@@ -3,21 +3,31 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.apache.http.entity.ContentType;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 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.api.client.ClientResponse.Status;
+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.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
 import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
 public class OAuth2AdminControllerTest extends OAuth2TestBase {
 
+    private String username = "OAuth2AdminControllerTest";
     private String adminAuthHeader;
     private String userAuthHeader;
 
@@ -33,8 +43,40 @@
                 .createBasicAuthorizationHeaderValue("dory", "password");
     }
 
+    private ClientResponse updateClientPrivilege (String username,
+            MultivaluedMap<String, String> form)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        ClientResponse response = resource().path(API_VERSION).path("oauth2")
+                .path("admin").path("client").path("privilege")
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        return response;
+    }
+    
+    private void updateClientPriviledge (String clientId, boolean isSuper)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+        form.add("super", Boolean.toString(isSuper));
+
+        ClientResponse response = updateClientPrivilege(username, form);
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+                node.at("/errors/0/0").asInt());
+
+        response = updateClientPrivilege("admin", form);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
     @Test
-    public void testCleanTokens () {
+    public void testCleanExpiredTokens () {
         int refreshTokensBefore =
                 refreshDao.retrieveInvalidRefreshTokens().size();
         assertTrue(refreshTokensBefore > 0);
@@ -51,10 +93,10 @@
     }
 
     @Test
-    public void testCleanTokenWithRevoke () throws KustvaktException {
+    public void testCleanRevokedTokens () throws KustvaktException {
 
         int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
-        
+
         String code = requestAuthorizationCode(publicClientId, "", null,
                 userAuthHeader);
 
@@ -67,7 +109,7 @@
         testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
 
         int accessTokensAfter = accessDao.retrieveInvalidAccessTokens().size();
-        assertEquals(accessTokensAfter,accessTokensBefore+1);
+        assertEquals(accessTokensAfter, accessTokensBefore + 1);
 
         resource().path(API_VERSION).path("oauth2").path("admin").path("token")
                 .path("clean").header(Attributes.AUTHORIZATION, adminAuthHeader)
@@ -75,5 +117,73 @@
 
         assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
     }
-    
+
+    @Test
+    public void testUpdateClientPrivilege () throws KustvaktException {
+        // register a client
+        ClientResponse response = registerConfidentialClient(username);
+        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();
+
+        updateClientPriviledge(clientId, true);
+        testAccessTokenAfterUpgradingClient(clientId, accessToken);
+
+        updateClientPriviledge(clientId, false);
+        testAccessTokenAfterDegradingSuperClient(clientId, accessToken);
+
+        deregisterConfidentialClient(username, clientId);
+    }
+
+    // old access tokens retain their scopes
+    private void testAccessTokenAfterUpgradingClient (String clientId,
+            String accessToken) throws KustvaktException {
+
+        JsonNode node = retrieveClientInfo(clientId, "admin");
+        assertTrue(node.at("/is_super").asBoolean());
+
+        // list vc
+        ClientResponse response = resource().path(API_VERSION).path("vc")
+                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+                .get(ClientResponse.class);
+
+        assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
+                response.getStatus());
+        String entity = response.getEntity(String.class);
+        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 {
+        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 is invalid",
+                node.at("/errors/0/1").asText());
+    }
 }
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 f688b31..1e73773 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
@@ -76,48 +76,6 @@
 
     }
 
-    private ClientResponse registerClient (String username,
-            OAuth2ClientJson json) throws UniformInterfaceException,
-            ClientHandlerException, KustvaktException {
-        return resource().path(API_VERSION).path("oauth2").path("client")
-                .path("register")
-                .header(Attributes.AUTHORIZATION,
-                        HttpAuthorizationHandler
-                                .createBasicAuthorizationHeaderValue(username,
-                                        "password"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .entity(json).post(ClientResponse.class);
-    }
-
-    private ClientResponse registerConfidentialClient ()
-            throws KustvaktException {
-
-        OAuth2ClientJson json = new OAuth2ClientJson();
-        json.setName("OAuth2ClientTest");
-        json.setType(OAuth2ClientType.CONFIDENTIAL);
-        json.setUrl("http://example.client.com");
-        json.setRedirectURI("https://example.client.com/redirect");
-        json.setDescription("This is a confidential test client.");
-
-        return registerClient(username, json);
-    }
-
-    private JsonNode retrieveClientInfo (String clientId, String username)
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
-        ClientResponse response = resource().path(API_VERSION).path("oauth2")
-                .path("client").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 testRetrieveClientInfo () throws KustvaktException {
         // public client
@@ -150,7 +108,7 @@
 
     @Test
     public void testRegisterConfidentialClient () throws KustvaktException {
-        ClientResponse response = registerConfidentialClient();
+        ClientResponse response = registerConfidentialClient(username);
         String entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         JsonNode node = JsonUtils.readTree(entity);
@@ -162,7 +120,7 @@
         assertFalse(clientId.contains("a"));
 
         testResetConfidentialClientSecret(clientId, clientSecret);
-        testDeregisterConfidentialClient(clientId);
+        deregisterConfidentialClient(username, clientId);
     }
 
     @Test
@@ -469,19 +427,6 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
 
-    private void testDeregisterConfidentialClient (String clientId)
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
-
-        ClientResponse response = resource().path(API_VERSION).path("oauth2")
-                .path("client").path("deregister").path(clientId)
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue(username, "pass"))
-                .delete(ClientResponse.class);
-
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-    }
-
     private void testResetPublicClientSecret (String clientId)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
@@ -531,81 +476,6 @@
         return newClientSecret;
     }
 
-    @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);
-
-        testDeregisterConfidentialClient(clientId);
-    }
-
-    // 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);
-        JsonNode node = retrieveClientInfo(clientId, "admin");
-        assertTrue(node.at("/is_super").asBoolean());
-
-        // list vc
-        ClientResponse response = resource().path(API_VERSION).path("vc")
-                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
-                .get(ClientResponse.class);
-
-        assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
-                response.getStatus());
-        String entity = response.getEntity(String.class);
-        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 is invalid",
-                node.at("/errors/0/1").asText());
-    }
-
     private void requestAuthorizedClientList (String userAuthHeader)
             throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
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
index e039bf1..9cf2e75 100644
--- 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
@@ -24,9 +24,11 @@
 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.OAuth2ClientType;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
 /**
  * Provides common methods and variables for OAuth2 tests,
@@ -214,19 +216,60 @@
         assertEquals("Refresh token has been revoked",
                 node.at("/error_description").asText());
     }
+    
+    protected ClientResponse registerClient (String username,
+            OAuth2ClientJson json) throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        return resource().path(API_VERSION).path("oauth2").path("client")
+                .path("register")
+                .header(Attributes.AUTHORIZATION,
+                        HttpAuthorizationHandler
+                                .createBasicAuthorizationHeaderValue(username,
+                                        "password"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .entity(json).post(ClientResponse.class);
+    }
+    
+    protected ClientResponse registerConfidentialClient (String username)
+            throws KustvaktException {
 
-    protected void updateClientPrivilege (MultivaluedMap<String, String> form)
+        OAuth2ClientJson json = new OAuth2ClientJson();
+        json.setName("OAuth2ClientTest");
+        json.setType(OAuth2ClientType.CONFIDENTIAL);
+        json.setUrl("http://example.client.com");
+        json.setRedirectURI("https://example.client.com/redirect");
+        json.setDescription("This is a confidential test client.");
+
+        return registerClient(username, json);
+    }
+    
+    protected void deregisterConfidentialClient (String username, String clientId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+
+        ClientResponse response = resource().path(API_VERSION).path("oauth2")
+                .path("client").path("deregister").path(clientId)
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .delete(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+    }
+
+    protected JsonNode retrieveClientInfo (String clientId, String username)
             throws UniformInterfaceException, ClientHandlerException,
             KustvaktException {
         ClientResponse response = resource().path(API_VERSION).path("oauth2")
-                .path("client").path("privilege")
+                .path("client").path(clientId)
                 .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("admin", "pass"))
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .entity(form).post(ClientResponse.class);
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .get(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        return JsonUtils.readTree(entity);
     }
 
     protected ClientResponse searchWithAccessToken (String accessToken) {