Allow admin access using admin token for the clean token API

Change-Id: Id3c6db09b59cd7384f508a5b1d19c0b3084892b9
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
index 1201529..885ce41 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
@@ -18,6 +18,9 @@
 import de.ids_mannheim.korap.web.KustvaktResponseHandler;
 
 /**
+ * Verifies admin credentials or token before allowing access to
+ * administrative services
+ * 
  * @author hanl, margaretha
  * 
  * @see {@link AuthenticationFilter}
@@ -29,6 +32,7 @@
     private @Context ServletContext servletContext;
     @Autowired
     private AdminDao adminDao;
+
     @Autowired
     private KustvaktResponseHandler kustvaktResponseHandler;
 
@@ -36,28 +40,37 @@
     public void filter (ContainerRequestContext context) {
         super.filter(context);
         String username = "guest";
-        
-        // legacy support for kustvakt core
-        String adminToken = JerseyUtils.getFormParameters(context).asMap().getFirst("token");
+        String adminToken = JerseyUtils.getFormParameters(context).asMap()
+                .getFirst("token");
+        if (!checkAdminToken(adminToken)) {
+            SecurityContext securityContext = context.getSecurityContext();
+            TokenContext tokenContext = (TokenContext) securityContext
+                    .getUserPrincipal();
+            checkAdminCredentials(tokenContext, username);
+        }
+    }
+
+
+    private boolean checkAdminToken (String adminToken) {
         if (adminToken != null && !adminToken.isEmpty()) {
-            // startswith token=
-            // adminToken = adminToken.substring(6);
             if (adminToken
                     .equals(servletContext.getInitParameter("adminToken"))) {
-                return;
+                return true;
             }
         }
+        return false;
+    }
 
-        SecurityContext securityContext = context.getSecurityContext();
-        TokenContext tokenContext = (TokenContext) securityContext
-                .getUserPrincipal();
-        
+
+    private void checkAdminCredentials (TokenContext tokenContext,
+            String username) {
         if (tokenContext != null) {
             username = tokenContext.getUsername();
             if (adminDao.isAdmin(username)) {
                 return;
             }
         }
+
         throw kustvaktResponseHandler.throwit(new KustvaktException(
                 StatusCodes.AUTHORIZATION_FAILED,
                 "Unauthorized operation for user: " + username, username));
diff --git a/full/Changes b/full/Changes
index 0ceb78d..e4c7a6c 100644
--- a/full/Changes
+++ b/full/Changes
@@ -11,6 +11,8 @@
 - Allowed OAuth2 clients to use localhost as redirect URIs.
 2023-02-03
 - Fixed content-type in error responses by changing it to application/json
+2023-02-06
+- Allow admin access using admin token for the clean token API
 
 # version 0.69.1
 
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 9027071..0ca1368 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
@@ -2,28 +2,21 @@
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
-import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import de.ids_mannheim.korap.web.utils.ResourceFilters;
-
-import de.ids_mannheim.korap.constant.OAuth2Scope;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.service.OAuth2AdminService;
-import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
-import de.ids_mannheim.korap.security.context.TokenContext;
 import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
 import de.ids_mannheim.korap.web.filter.APIVersionFilter;
 import de.ids_mannheim.korap.web.filter.AdminFilter;
+import de.ids_mannheim.korap.web.utils.ResourceFilters;
 
 @Controller
 @Path("{version}/oauth2/admin")
@@ -34,26 +27,18 @@
     @Autowired
     private OAuth2AdminService adminService;
     @Autowired
-    private OAuth2ScopeService scopeService;
-    @Autowired
     private OAuth2ResponseHandler responseHandler;
 
-    @GET
+    /**
+     * Removes expired or invalid access and refresh tokens from
+     * database and cache
+     * 
+     * @return Response status OK, if successful
+     */
+    @POST
     @Path("token/clean")
-    public Response cleanExpiredInvalidToken (
-            @Context SecurityContext securityContext) {
-
-        TokenContext context =
-                (TokenContext) securityContext.getUserPrincipal();
-
-        try {
-            scopeService.verifyScope(context, OAuth2Scope.ADMIN);
-            adminService.cleanTokens();
-
-        }
-        catch (KustvaktException e) {
-            throw responseHandler.throwit(e);
-        }
+    public Response cleanExpiredInvalidToken () {
+        adminService.cleanTokens();
         return Response.ok().build();
     }
 
@@ -68,7 +53,6 @@
      * When degrading super clients, all existing tokens and
      * authorization codes are invalidated.
      * 
-     * @param securityContext
      * @param clientId
      *            OAuth2 client id
      * @param super
@@ -79,13 +63,9 @@
     @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();
         }
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 db29dec..42431c8 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,8 +3,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Form;
-import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 
 import org.apache.http.entity.ContentType;
 import org.junit.Test;
@@ -12,10 +15,6 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
-import javax.ws.rs.ProcessingException;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.Response.Status;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
@@ -59,25 +58,37 @@
         return response;
     }
     
-    private void updateClientPriviledge (String clientId, boolean isSuper)
+    private Response updateClientPrivilegeWithAdminToken (String clientId)
             throws ProcessingException,
             KustvaktException {
+        
         Form form = new Form();
         form.param("client_id", clientId);
-        form.param("super", Boolean.toString(isSuper));
+        form.param("super", Boolean.toString(false));
+        form.param("token", "secret"); //adminToken
+        
+        Response response = target().path(API_VERSION).path("oauth2")
+                .path("admin").path("client").path("privilege")
+                .request()
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(form));
 
+        return response;
+    }
+    
+    
+    private void testUpdateClientPriviledgeUnauthorized (Form form)
+            throws ProcessingException, KustvaktException {
         Response response = updateClientPrivilege(username, form);
         JsonNode node = JsonUtils.readTree(response.readEntity(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 testCleanExpiredTokens () {
+    public void testCleanExpiredTokensUsingAdminToken () {
         int refreshTokensBefore =
                 refreshDao.retrieveInvalidRefreshTokens().size();
         assertTrue(refreshTokensBefore > 0);
@@ -85,11 +96,13 @@
         int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
         assertTrue(accessTokensBefore > 0);
 
+        Form form = new Form();
+        form.param("token", "secret");
+        
         target().path(API_VERSION).path("oauth2").path("admin").path("token")
                 .path("clean")
                 .request()
-                .header(Attributes.AUTHORIZATION, adminAuthHeader)
-                .get();
+                .post(Entity.form(form));
 
         assertEquals(0, refreshDao.retrieveInvalidRefreshTokens().size());
         assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
@@ -116,7 +129,7 @@
                 .path("clean")
                 .request()
                 .header(Attributes.AUTHORIZATION, adminAuthHeader)
-                .get();
+                .post(null);
 
         assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
     }
@@ -137,10 +150,22 @@
                 clientAuthHeader);
         String accessToken = node.at("/access_token").asText();
 
-        updateClientPriviledge(clientId, true);
+        //update client priviledge to super client
+        Form form = new Form();
+        form.param("client_id", clientId);
+        form.param("super", Boolean.toString(true));
+        
+        testUpdateClientPriviledgeUnauthorized(form);
+        
+        response = updateClientPrivilege("admin", form);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        
         testAccessTokenAfterUpgradingClient(clientId, accessToken);
 
-        updateClientPriviledge(clientId, false);
+        // degrade a super client to a common client
+        
+        updateClientPrivilegeWithAdminToken(clientId);
+        
         testAccessTokenAfterDegradingSuperClient(clientId, accessToken);
 
         deregisterClient(username, clientId);