Merge branch 'master' into 0.75-SNAPSHOT

Change-Id: I26330d52478552b16aa68080b6590880bfb60192
diff --git a/Changes b/Changes
index 2bda9a9..ef15a5d 100644
--- a/Changes
+++ b/Changes
@@ -19,6 +19,12 @@
 - Removed mail configuration (#764)
 - Deprecate VC access deletion.
 
+# version 0.74.1-SNAPSHOT
+
+- Switch Docker image to temurin (diewald).
+- - Introduce filter_by and deprecate authorized_only in OAuth2
+  client list (close #579)
+
 # version 0.74
 
 - Remove corpusQuery param in the statistics web-service (close #758).
diff --git a/Dockerfile b/Dockerfile
index 2b138d1..ae5beb1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 # Use alpine linux as base image
-FROM openjdk:19-alpine AS builder
+FROM eclipse-temurin:22-jdk-alpine AS builder
 
 # Copy repository respecting .dockerignore
 COPY . /kustvakt
@@ -94,7 +94,7 @@
 
 CMD ["sh"]
 
-FROM openjdk:19-alpine AS kustvakt-lite
+FROM eclipse-temurin:22-jre-alpine AS kustvakt-lite
 
 RUN addgroup -S korap && \
     adduser -S kustvakt -G korap && \
@@ -115,7 +115,7 @@
 
 CMD [ "Kustvakt-lite.jar" ]
 
-FROM openjdk:19-alpine AS kustvakt-full
+FROM eclipse-temurin:22-jre-alpine AS kustvakt-full
 
 RUN addgroup -S korap && \
     adduser -S kustvakt -G korap && \
diff --git a/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java b/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
index 6f45900..6f7ab07 100644
--- a/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
+++ b/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2ClientInfoDto.java
@@ -19,7 +19,7 @@
  *
  */
 @JsonInclude(Include.NON_EMPTY)
-public class OAuth2ClientInfoDto {
+public class OAuth2ClientInfoDto implements Comparable<OAuth2ClientInfoDto>{
     @JsonProperty("super")
     private boolean isSuper;
 
@@ -84,6 +84,12 @@
             }
         } 
     }
+    
+    @Override
+    public int compareTo (OAuth2ClientInfoDto o) {
+        return this.getClientName().compareTo(o.getClientName());
+    }
+
 
     public boolean isSuper () {
         return isSuper;
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index ee2d72f..1c41998 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -7,6 +7,7 @@
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientDto;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientInfoDto;
 import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
@@ -16,7 +17,6 @@
 import de.ids_mannheim.korap.web.filter.APIVersionFilter;
 import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
 import de.ids_mannheim.korap.web.filter.BlockingFilter;
-import de.ids_mannheim.korap.web.filter.DemoFilter;
 import de.ids_mannheim.korap.web.filter.DemoUserFilter;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 import de.ids_mannheim.korap.web.utils.ResourceFilters;
@@ -220,7 +220,8 @@
             @Context SecurityContext context,
             @FormParam("super_client_id") String superClientId,
             @FormParam("super_client_secret") String superClientSecret,
-            @FormParam("authorized_only") boolean authorizedOnly) {
+            @FormParam("authorized_only") boolean authorizedOnly, // deprecated
+            @FormParam("filter_by") String filterBy) {
 
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
@@ -230,12 +231,34 @@
                     OAuth2Scope.LIST_USER_CLIENT);
 
             clientService.verifySuperClient(superClientId, superClientSecret);
+            
+            List<OAuth2ClientInfoDto> clients = null; 
+            
             if (authorizedOnly) {
-                return clientService.listUserAuthorizedClients(username);
+                clients = clientService.listUserAuthorizedClients(username);
             }
             else {
-                return clientService.listUserRegisteredClients(username);
+                if (filterBy !=null && !filterBy.isEmpty()) {
+                    if (filterBy.equals("authorized_only")) {
+                        clients = clientService.listUserAuthorizedClients(username);
+                    }
+                    else if (filterBy.equals("owned_only")) {
+                        clients = clientService.listUserRegisteredClients(username); 
+                    }
+                    else {
+                        throw new KustvaktException(
+                                StatusCodes.UNSUPPORTED_VALUE, "filter_by");
+                    }
+                }
+                else {               
+//                    clients = clientService.listUserAuthorizedClients(username);
+//                    clients.addAll(clientService.listUserRegisteredClients(username));
+                
+                    clients = clientService.listUserRegisteredClients(username);
+                }
             }
+            
+            return clients;
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
index 4f0f2cc..d537584 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
@@ -6,16 +6,9 @@
 
 import java.io.IOException;
 
-import jakarta.ws.rs.client.Entity;
-import jakarta.ws.rs.core.Form;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.Response.Status;
-
-import org.apache.http.entity.ContentType;
 import org.junit.jupiter.api.Test;
+
 import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.net.HttpHeaders;
-import com.nimbusds.oauth2.sdk.GrantType;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
@@ -25,6 +18,8 @@
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.controller.usergroup.UserGroupTestBase;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
 
 public class OAuth2AccessTokenTest extends UserGroupTestBase {
 
@@ -39,7 +34,7 @@
                 .createBasicAuthorizationHeaderValue(confidentialClientId,
                         clientSecret);
     }
-
+    
     @Test
     public void testScopeWithSuperClient () throws KustvaktException {
         Response response = requestTokenWithDoryPassword(superClientId,
@@ -74,6 +69,8 @@
                 confidentialClientId, clientSecret, code);
         JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
         String token = node.at("/access_token").asText();
+        String refreshToken = node.at("/refresh_token").asText();
+        
         assertTrue(node.at("/scope").asText()
                 .contains(OAuth2Scope.VC_INFO.toString()));
         // test list vc using the token
@@ -82,6 +79,11 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         node = JsonUtils.readTree(response.readEntity(String.class));
         assertEquals(3, node.size());
+        
+        revokeToken(token, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
+        revokeToken(refreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 
     @Test
@@ -93,9 +95,15 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
         String accessToken = node.at("/access_token").asText();
+        String refreshToken = node.at("/refresh_token").asText();
         testScopeNotAuthorized(accessToken);
         testScopeNotAuthorize2(accessToken);
         testSearchWithOAuth2Token(accessToken);
+        
+        revokeToken(accessToken, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
+        revokeToken(refreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 
     private void testScopeNotAuthorized (String accessToken)
@@ -149,17 +157,14 @@
         JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
                 confidentialClientId, code, clientAuthHeader);
         String accessToken = node.at("/access_token").asText();
-        Form form = new Form();
-        form.param("token", accessToken);
-        form.param("client_id", confidentialClientId);
-        form.param("client_secret", "secret");
-        Response response = target().path(API_VERSION).path("oauth2")
-                .path("revoke").request()
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .post(Entity.form(form));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        String refreshToken = node.at("/refresh_token").asText();
+        
+        revokeToken(accessToken, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
         testSearchWithRevokedAccessToken(accessToken);
+        
+        revokeToken(refreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 
     @Test
@@ -170,7 +175,7 @@
                 publicClientId, "", code);
         JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
         String accessToken = node.at("/access_token").asText();
-        testRevokeTokenViaSuperClient(accessToken, userAuthHeader);
+        revokeTokenViaSuperClient(accessToken, userAuthHeader);
         testSearchWithRevokedAccessToken(accessToken);
     }
 
@@ -183,23 +188,18 @@
                 confidentialClientId, code, clientAuthHeader);
         String accessToken = node.at("/access_token").asText();
         String refreshToken = node.at("/refresh_token").asText();
-        Form form = new Form();
-        form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
-        form.param("client_id", confidentialClientId);
-        form.param("client_secret", "secret");
-        form.param("refresh_token", refreshToken);
-        Response response = target().path(API_VERSION).path("oauth2")
-                .path("token").request()
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .post(Entity.form(form));
-        String entity = response.readEntity(String.class);
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        node = JsonUtils.readTree(entity);
-        assertNotNull(node.at("/access_token").asText());
-        assertTrue(!refreshToken.equals(node.at("/refresh_token").asText()));
+
+        node = requestTokenWithRefreshToken(confidentialClientId, clientSecret,
+                refreshToken);
+        String newAccessToken = node.at("/access_token").asText();
+        String newRefreshToken = node.at("/refresh_token").asText();
+        assertTrue(!refreshToken.equals(newRefreshToken));
+
         testSearchWithRevokedAccessToken(accessToken);
+        revokeToken(newAccessToken, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
+        revokeToken(newRefreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 
     @Test
@@ -210,6 +210,7 @@
         JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
                 confidentialClientId, code, clientAuthHeader);
         String userAuthToken = node.at("/access_token").asText();
+        String refreshToken = node.at("/refresh_token").asText();
         Response response = requestAuthorizationCode("code",
                 confidentialClientId, "", "search", "",
                 "Bearer " + userAuthToken);
@@ -219,6 +220,11 @@
                 node.at("/errors/0/0").asInt());
         assertEquals(node.at("/errors/0/1").asText(),
                 "Scope authorize is not authorized");
+
+        revokeToken(userAuthToken, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
+        revokeToken(refreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 
     @Test
@@ -236,5 +242,7 @@
         String code = requestAuthorizationCode(superClientId,
                 "Bearer " + userAuthToken);
         assertNotNull(code);
+        
+        revokeTokenViaSuperClient(userAuthToken, userAuthHeader);
     }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
index fabc267..f5a8a61 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
@@ -114,7 +114,7 @@
         String entity = response.readEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
         String accessToken = node.at("/access_token").asText();
-        testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
+        revokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
         int accessTokensAfter = accessDao.retrieveInvalidAccessTokens().size();
         assertEquals(accessTokensAfter, accessTokensBefore + 1);
         target().path(API_VERSION).path("admin").path("oauth2").path("token")
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java
index 8372acc..9225220 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AuthorizationPostTest.java
@@ -78,10 +78,15 @@
                 confidentialClientId, clientSecret, code);
         String entity = response.readEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
-        assertNotNull(node.at("/access_token").asText());
-        assertNotNull(node.at("/refresh_token").asText());
+        String token = node.at("/access_token").asText();
+        String refreshToken = node.at("/refresh_token").asText();
         assertEquals(TokenType.BEARER.displayName(),
                 node.at("/token_type").asText());
         assertNotNull(node.at("/expires_in").asText());
+        
+        revokeToken(token, confidentialClientId, clientSecret,
+                ACCESS_TOKEN_TYPE);
+        revokeToken(refreshToken, confidentialClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
     }
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index 20179fb..fd74696 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -102,6 +102,7 @@
         assertFalse(clientId.contains("a"));
         testListConfidentialClient(username, clientId);
         testConfidentialClientInfo(clientId, username);
+//        testListAllUserClients(username);
         testResetConfidentialClientSecret(clientId, clientSecret);
         deregisterClient(username, clientId);
     }
@@ -292,7 +293,7 @@
         assertNotNull(clientId);
         assertTrue(node.at("/client_secret").isMissingNode());
         
-        node = listUserClients(username);
+        node = listUserClients(username,"owned_only");
         assertFalse(node.at("/0/client_redirect_uri").isMissingNode());
         assertFalse(node.at("/0/registration_date").isMissingNode());
         assertEquals(username,
@@ -508,7 +509,10 @@
     private void testListAuthorizedClients (String userAuthHeader)
             throws KustvaktException {
         Form form = getSuperClientForm();
-        form.param("authorized_only", "true");
+        // deprecated, use filter_by = authorized_only instead
+//        form.param("authorized_only", "true");
+        
+        form.param("filter_by", "authorized_only");
         Response response = target().path(API_VERSION).path("oauth2")
                 .path("client").path("list").request()
                 .header(Attributes.AUTHORIZATION, userAuthHeader)
@@ -535,7 +539,7 @@
         OAuth2ClientJson json = createOAuth2ClientJson(clientName,
                 OAuth2ClientType.PUBLIC, "Dory's client.");
         registerClient("dory", json);
-        JsonNode node = listUserClients("dory");
+        JsonNode node = listUserClients("dory","owned_only");
         assertEquals(1, node.size());
         assertEquals(clientName, node.at("/0/client_name").asText());
         assertEquals(OAuth2ClientType.PUBLIC.name(),
@@ -544,12 +548,16 @@
         assertFalse(node.at("/0/registration_date").isMissingNode());
         assertTrue(node.at("/refresh_token_expiry").isMissingNode());
         String clientId = node.at("/0/client_id").asText();
+        
+//        testListAllUserClients("dory");
         testDeregisterPublicClient(clientId, "dory");
     }
 
     private void testListConfidentialClient (String username, String clientId)
             throws ProcessingException, KustvaktException {
-        JsonNode node = listUserClients(username);
+        // means authorized_only = false
+        // this is deprecated, filter_by = owned_only should be use instead.
+        JsonNode node = listUserClients(username,"");
         assertEquals(1, node.size());
         assertEquals(clientId, node.at("/0/client_id").asText());
         assertEquals(node.at("/0/client_name").asText(), "OAuth2ClientTest");
@@ -566,8 +574,27 @@
         assertTrue(node.at("/0/source").isMissingNode());
     }
 
+    // not ready until the behavior for (filterBy=null || filterBy.isEmpty) is set
+    private void testListAllUserClients (String username) throws KustvaktException {
+        // authorize
+        String userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue(username, "password");
+        String code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
+        Response response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, this.clientSecret, code);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
+        String accessToken = node.at("/access_token").asText();
+        
+        node = listUserClients(username,null);
+        assertEquals(2, node.size());
+        
+        testRevokeAllTokenViaSuperClient(confidentialClientId, userAuthHeader,
+                accessToken);
+    }
+    
     @Test
-    public void testListUserClients () throws KustvaktException {
+    public void testListAuthorizedUserClients () throws KustvaktException {
         String username = "pearl";
         String password = "pwd";
         userAuthHeader = HttpAuthorizationHandler
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index ab2eb27..dfbb987 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -57,7 +57,7 @@
         assertEquals(TokenType.BEARER.displayName(),
                 node.at("/token_type").asText());
         assertEquals(31536000, node.at("/expires_in").asInt());
-        testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
+        revokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
         assertTrue(node.at("/refresh_token").isMissingNode());
     }
 
@@ -487,7 +487,7 @@
         assertTrue(!newRefreshToken.equals(refreshToken));
         testRequestTokenWithRevokedRefreshToken(clientId, clientSecret,
                 refreshToken);
-        testRevokeToken(newRefreshToken, clientId, clientSecret,
+        revokeToken(newRefreshToken, clientId, clientSecret,
                 REFRESH_TOKEN_TYPE);
         testRequestTokenWithRevokedRefreshToken(clientId, clientSecret,
                 newRefreshToken);
@@ -654,24 +654,24 @@
         node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE,
                 confidentialClientId);
         assertEquals(2, node.size());
-        testRevokeToken(refreshToken1, superClientId, clientSecret,
+        revokeToken(refreshToken1, superClientId, clientSecret,
                 REFRESH_TOKEN_TYPE);
-        testRevokeToken(node.at("/0/token").asText(), confidentialClientId,
+        revokeToken(node.at("/0/token").asText(), confidentialClientId,
                 clientSecret, REFRESH_TOKEN_TYPE);
-        testRevokeToken(node.at("/1/token").asText(), confidentialClientId2,
+        revokeToken(node.at("/1/token").asText(), confidentialClientId2,
                 clientSecret, REFRESH_TOKEN_TYPE);
         node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
         assertEquals(1, node.size());
-        testRevokeTokenViaSuperClient(node.at("/0/token").asText(),
+        revokeTokenViaSuperClient(node.at("/0/token").asText(),
                 userAuthHeader);
         node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
         assertEquals(0, node.size());
         // try revoking a token belonging to another user
         // should not return any errors
-        testRevokeTokenViaSuperClient(refreshToken5, userAuthHeader);
+        revokeTokenViaSuperClient(refreshToken5, userAuthHeader);
         node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
         assertEquals(1, node.size());
-        testRevokeTokenViaSuperClient(refreshToken5, darlaAuthHeader);
+        revokeTokenViaSuperClient(refreshToken5, darlaAuthHeader);
         node = requestTokenList(darlaAuthHeader, REFRESH_TOKEN_TYPE);
         assertEquals(0, node.size());
     }
@@ -702,7 +702,7 @@
         // list refresh tokens
         node = requestTokenList(userAuthHeader, REFRESH_TOKEN_TYPE);
         assertEquals(0, node.size());
-        testRevokeTokenViaSuperClient(accessToken1, userAuthHeader);
+        revokeTokenViaSuperClient(accessToken1, userAuthHeader);
         node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
         // System.out.println(node);
         assertEquals(1, node.size());
@@ -715,7 +715,7 @@
         assertNotNull(node.at("/0/client_name").asText());
         assertNotNull(node.at("/0/client_description").asText());
         assertNotNull(node.at("/0/client_url").asText());
-        testRevokeTokenViaSuperClient(accessToken2, userAuthHeader);
+        revokeTokenViaSuperClient(accessToken2, userAuthHeader);
         node = requestTokenList(userAuthHeader, ACCESS_TOKEN_TYPE);
         assertEquals(0, node.size());
     }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
index a8c2139..e150d5d 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2PluginTest.java
@@ -113,7 +113,7 @@
     private void testListUserRegisteredPlugins (String username,
             String clientId, String clientName, int refreshTokenExpiry)
             throws ProcessingException, KustvaktException {
-        JsonNode node = listUserClients(username);
+        JsonNode node = listUserClients(username, "owned_only");
         assertEquals(1, node.size());
         assertEquals(clientId, node.at("/0/client_id").asText());
         assertEquals(clientName, node.at("/0/client_name").asText());
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index 4ce03d1..8a20196 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -60,7 +60,7 @@
 public abstract class OAuth2TestBase extends SpringJerseyTest {
 
     @Autowired
-    private AccessTokenDao tokenDao;
+    protected AccessTokenDao tokenDao;
     @Autowired
     private OAuth2ClientDao clientDao;
     @Autowired
@@ -255,6 +255,24 @@
         return requestToken(form);
     }
 
+    protected JsonNode requestTokenWithRefreshToken (String clientId,
+            String clientSecret, String refreshToken) throws KustvaktException {
+        Form form = new Form();
+        form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+        form.param("client_id", clientId);
+        form.param("client_secret", clientSecret);
+        form.param("refresh_token", refreshToken);
+        Response response = target().path(API_VERSION).path("oauth2")
+                .path("token").request()
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(form));
+        String entity = response.readEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        return JsonUtils.readTree(entity);
+    }
+    
     protected void testRequestTokenWithRevokedRefreshToken (String clientId,
             String clientSecret, String refreshToken) throws KustvaktException {
         Form form = new Form();
@@ -280,6 +298,44 @@
         assertEquals("Refresh token has been revoked",
                 node.at("/error_description").asText());
     }
+    
+    protected void revokeTokenViaSuperClient (String token,
+            String userAuthHeader) {
+        Form form = new Form();
+        form.param("token", token);
+        form.param("super_client_id", superClientId);
+        form.param("super_client_secret", clientSecret);
+
+        Response response = target().path(API_VERSION).path("oauth2")
+                .path("revoke").path("super").request()
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .header(Attributes.AUTHORIZATION, userAuthHeader)
+                .post(Entity.form(form));
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        assertEquals("SUCCESS", response.readEntity(String.class));
+    }
+
+    protected void revokeToken (String token, String clientId,
+            String clientSecret, String tokenType) {
+        Form form = new Form();
+        form.param("token_type", tokenType);
+        form.param("token", token);
+        form.param("client_id", clientId);
+        if (clientSecret != null) {
+            form.param("client_secret", clientSecret);
+        }
+
+        Response response = target().path(API_VERSION).path("oauth2")
+                .path("revoke").request()
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(form));
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        assertEquals("SUCCESS", response.readEntity(String.class));
+    }
 
     protected Response registerClient (String username, OAuth2ClientJson json)
             throws ProcessingException, KustvaktException {
@@ -392,55 +448,21 @@
                 node.at("/errors/0/1").asText());
     }
 
-    protected void testRevokeTokenViaSuperClient (String token,
-            String userAuthHeader) {
-        Form form = new Form();
-        form.param("token", token);
-        form.param("super_client_id", superClientId);
-        form.param("super_client_secret", clientSecret);
-
-        Response response = target().path(API_VERSION).path("oauth2")
-                .path("revoke").path("super").request()
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .header(Attributes.AUTHORIZATION, userAuthHeader)
-                .post(Entity.form(form));
-
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        assertEquals("SUCCESS", response.readEntity(String.class));
-    }
-
-    protected void testRevokeToken (String token, String clientId,
-            String clientSecret, String tokenType) {
-        Form form = new Form();
-        form.param("token_type", tokenType);
-        form.param("token", token);
-        form.param("client_id", clientId);
-        if (clientSecret != null) {
-            form.param("client_secret", clientSecret);
-        }
-
-        Response response = target().path(API_VERSION).path("oauth2")
-                .path("revoke").request()
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .post(Entity.form(form));
-
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        assertEquals("SUCCESS", response.readEntity(String.class));
-    }
-
-    protected JsonNode listUserClients (String username)
+    protected JsonNode listUserClients (String username, String filterBy)
             throws ProcessingException, KustvaktException {
         Form form = getSuperClientForm();
+        
+        if (filterBy != null) {
+            form.param("filter_by", filterBy);
+        }
         Response response = target().path(API_VERSION).path("oauth2")
-                .path("client").path("list").request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue(username, "pwd"))
-                .header(HttpHeaders.CONTENT_TYPE,
-                        ContentType.APPLICATION_FORM_URLENCODED)
-                .post(Entity.form(form));
-
+            .path("client").path("list").request()
+            .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                    .createBasicAuthorizationHeaderValue(username, "pwd"))
+            .header(HttpHeaders.CONTENT_TYPE,
+                    ContentType.APPLICATION_FORM_URLENCODED)
+            .post(Entity.form(form));
+        
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
         String entity = response.readEntity(String.class);
diff --git a/src/test/resources/test-config.xml b/src/test/resources/test-config.xml
index 5721077..5811a33 100644
--- a/src/test/resources/test-config.xml
+++ b/src/test/resources/test-config.xml
@@ -39,10 +39,12 @@
 		<property name="locations">
 			<array>
 				<value>classpath:test-jdbc.properties</value>
-				<value>file:./test-jdbc.properties</value>
+				<value>file:./data/test-jdbc.properties</value>
 				<value>classpath:test-hibernate.properties</value>
+				<value>file:./data/test-hibernate.properties</value>
 				<value>file:./kustvakt-test.conf</value>
 				<value>classpath:kustvakt-test.conf</value>
+				<value>file:./data/kustvakt-test.conf</value>
 			</array>
 		</property>
 	</bean>