Added a service to revoke a single refresh token via super client

Change-Id: I94ef5f32d96c3658f92aad870582b3be7f67c5db
diff --git a/full/Changes b/full/Changes
index b3f5ce9..733bb9f 100644
--- a/full/Changes
+++ b/full/Changes
@@ -26,6 +26,9 @@
    - Added a service to list active refresh tokens of a user (margaretha)
    - Added username filtering to token revocation service via super client 
      (margaretha)
+26/11/2019
+   - Added a service to revoke a single refresh token via super client 
+     (margaretha)  
 
 # version 0.62.1
 08/07/2019
@@ -37,7 +40,8 @@
      (margaretha)
    - Fixed post requests with status OK and empty body (margaretha)
 07/08/2019
-   - Resolved #40 (margaretha)
+   - Added users to hidden group when searching in a published VC (margaretha, 
+     resolved #40)
 15/08/2019
    - Updated the response statuses of VC PUT requests (margaretha)
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
index da65ccd..1b1be95 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
@@ -12,7 +12,6 @@
 import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Join;
 import javax.persistence.criteria.Root;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,10 +27,7 @@
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken_;
-import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
-import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_;
 import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
-import de.ids_mannheim.korap.oauth2.entity.RefreshToken_;
 import de.ids_mannheim.korap.utils.ParameterChecker;
 
 /**
@@ -130,16 +126,4 @@
         return q.getResultList();
     }
 
-    public List<RefreshToken> retrieveRefreshTokenByClientId (String clientId) {
-        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
-        CriteriaQuery<RefreshToken> query =
-                builder.createQuery(RefreshToken.class);
-        Root<RefreshToken> root = query.from(RefreshToken.class);
-        Join<RefreshToken, OAuth2Client> client =
-                root.join(RefreshToken_.client);
-        query.select(root);
-        query.where(builder.equal(client.get(OAuth2Client_.id), clientId));
-        TypedQuery<RefreshToken> q = entityManager.createQuery(query);
-        return q.getResultList();
-    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
index 339f3cd..71bafb5 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
@@ -6,6 +6,7 @@
 import java.util.Set;
 
 import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
 import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
 import javax.persistence.TypedQuery;
@@ -70,9 +71,19 @@
         entityManager.persist(token);
         return token;
     }
+    
+    public RefreshToken updateRefreshToken (RefreshToken token)
+            throws KustvaktException {
+        ParameterChecker.checkObjectValue(token, "refresh_token");
+
+        token = entityManager.merge(token);
+        return token;
+    }
 
     public RefreshToken retrieveRefreshToken (String token)
             throws KustvaktException {
+        ParameterChecker.checkStringValue(token, "refresh token");
+        
         CriteriaBuilder builder = entityManager.getCriteriaBuilder();
         CriteriaQuery<RefreshToken> query =
                 builder.createQuery(RefreshToken.class);
@@ -84,6 +95,32 @@
         Query q = entityManager.createQuery(query);
         return (RefreshToken) q.getSingleResult();
     }
+    
+    public RefreshToken retrieveRefreshToken (String token, String username)
+            throws KustvaktException {
+        
+        ParameterChecker.checkStringValue(token, "refresh token");
+        ParameterChecker.checkStringValue(username, "username");
+        
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<RefreshToken> query =
+                builder.createQuery(RefreshToken.class);
+        
+        Root<RefreshToken> root = query.from(RefreshToken.class);
+        Predicate condition = builder.and(
+                builder.equal(root.get(RefreshToken_.userId), username),
+                builder.equal(root.get(RefreshToken_.token), token));
+        
+        query.select(root);
+        query.where(condition);
+        TypedQuery<RefreshToken> q = entityManager.createQuery(query);
+        try {
+            return q.getSingleResult();
+        }
+        catch (NoResultException e) {
+            return null;
+        }
+    }
 
     public List<RefreshToken> retrieveRefreshTokenByClientId (String clientId)
             throws KustvaktException {
@@ -101,14 +138,6 @@
         return q.getResultList();
     }
 
-    public RefreshToken updateRefreshToken (RefreshToken token)
-            throws KustvaktException {
-        ParameterChecker.checkObjectValue(token, "refresh_token");
-
-        token = entityManager.merge(token);
-        return token;
-    }
-
     public List<RefreshToken> retrieveRefreshTokenByUser (String username)
             throws KustvaktException {
         ParameterChecker.checkStringValue(username, "username");
@@ -131,4 +160,5 @@
         TypedQuery<RefreshToken> q = entityManager.createQuery(query);
         return q.getResultList();
     }
+    
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeAllTokenSuperRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeAllTokenSuperRequest.java
new file mode 100644
index 0000000..30ccafb
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeAllTokenSuperRequest.java
@@ -0,0 +1,70 @@
+package de.ids_mannheim.korap.oauth2.oltu;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.as.request.OAuthRequest;
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+import org.apache.oltu.oauth2.common.validators.OAuthValidator;
+
+/**
+ * A custom request based on {@link OAuthRequest}. It defines a
+ * request to revoke all tokens of a client. The request must have
+ * been sent from a super client.
+ * 
+ * @author margaretha
+ *
+ */
+public class OAuth2RevokeAllTokenSuperRequest {
+    protected HttpServletRequest request;
+    protected OAuthValidator<HttpServletRequest> validator;
+    protected Map<String, Class<? extends OAuthValidator<HttpServletRequest>>> validators =
+            new HashMap<String, Class<? extends OAuthValidator<HttpServletRequest>>>();
+
+    public OAuth2RevokeAllTokenSuperRequest () {
+        // TODO Auto-generated constructor stub
+    }
+
+    public OAuth2RevokeAllTokenSuperRequest (HttpServletRequest request)
+            throws OAuthSystemException, OAuthProblemException {
+        this.request = request;
+        validate();
+    }
+
+    protected void validate ()
+            throws OAuthSystemException, OAuthProblemException {
+        validator = initValidator();
+        validator.validateMethod(request);
+        validator.validateContentType(request);
+        validator.validateRequiredParameters(request);
+        // for super client authentication
+        validator.validateClientAuthenticationCredentials(request);
+    }
+
+    protected OAuthValidator<HttpServletRequest> initValidator ()
+            throws OAuthProblemException, OAuthSystemException {
+        return OAuthUtils.instantiateClass(RevokeAllTokenSuperValidator.class);
+    }
+
+    public String getParam (String name) {
+        return request.getParameter(name);
+    }
+
+    public String getClientId () {
+        return request.getParameter(OAuth.OAUTH_CLIENT_ID);
+    }
+
+    public String getSuperClientId () {
+        return request.getParameter(RevokeTokenSuperValidator.SUPER_CLIENT_ID);
+    }
+
+    public String getSuperClientSecret () {
+        return request
+                .getParameter(RevokeTokenSuperValidator.SUPER_CLIENT_SECRET);
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeTokenSuperRequest.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeTokenSuperRequest.java
index 347a233..f3c506d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeTokenSuperRequest.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/OAuth2RevokeTokenSuperRequest.java
@@ -6,7 +6,6 @@
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.oltu.oauth2.as.request.OAuthRequest;
-import org.apache.oltu.oauth2.common.OAuth;
 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
 import org.apache.oltu.oauth2.common.utils.OAuthUtils;
@@ -19,12 +18,12 @@
  * @author margaretha
  *
  */
-public class OAuth2RevokeTokenSuperRequest {
+public class OAuth2RevokeTokenSuperRequest{
     protected HttpServletRequest request;
     protected OAuthValidator<HttpServletRequest> validator;
     protected Map<String, Class<? extends OAuthValidator<HttpServletRequest>>> validators =
             new HashMap<String, Class<? extends OAuthValidator<HttpServletRequest>>>();
-
+    
     public OAuth2RevokeTokenSuperRequest () {
         // TODO Auto-generated constructor stub
     }
@@ -44,7 +43,6 @@
         // for super client authentication
         validator.validateClientAuthenticationCredentials(request);
     }
-
     protected OAuthValidator<HttpServletRequest> initValidator ()
             throws OAuthProblemException, OAuthSystemException {
         return OAuthUtils.instantiateClass(RevokeTokenSuperValidator.class);
@@ -53,11 +51,11 @@
     public String getParam (String name) {
         return request.getParameter(name);
     }
-
-    public String getClientId () {
-        return request.getParameter(OAuth.OAUTH_CLIENT_ID);
+    
+    public String getToken () {
+        return getParam("token");
     }
-
+    
     public String getSuperClientId () {
         return request.getParameter(RevokeTokenSuperValidator.SUPER_CLIENT_ID);
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeAllTokenSuperValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeAllTokenSuperValidator.java
new file mode 100644
index 0000000..b8a4782
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeAllTokenSuperValidator.java
@@ -0,0 +1,64 @@
+package de.ids_mannheim.korap.oauth2.oltu;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+import org.apache.oltu.oauth2.common.validators.AbstractValidator;
+
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+
+/**
+ * Defines required request parameters for
+ * OAuth2RevokeAllTokenSuperRequest and validates the request method.
+ * 
+ * @author margaretha
+ *
+ */
+public class RevokeAllTokenSuperValidator
+        extends AbstractValidator<HttpServletRequest> {
+
+    public static final String SUPER_CLIENT_ID = "super_client_id";
+    public static final String SUPER_CLIENT_SECRET = "super_client_secret";
+
+    public RevokeAllTokenSuperValidator () {
+        requiredParams.add(OAuth.OAUTH_CLIENT_ID);
+        requiredParams.add(SUPER_CLIENT_ID);
+        requiredParams.add(SUPER_CLIENT_SECRET);
+
+        enforceClientAuthentication = true;
+    }
+
+    @Override
+    public void validateMethod (HttpServletRequest request)
+            throws OAuthProblemException {
+        String method = request.getMethod();
+        if (!OAuth.HttpMethod.POST.equals(method)) {
+            throw OAuthProblemException.error(OAuth2Error.INVALID_REQUEST)
+                    .description("Method not correct.");
+        }
+    }
+
+    @Override
+    public void validateClientAuthenticationCredentials (
+            HttpServletRequest request) throws OAuthProblemException {
+        if (enforceClientAuthentication) {
+            Set<String> missingParameters = new HashSet<String>();
+
+            if (OAuthUtils.isEmpty(request.getParameter(SUPER_CLIENT_ID))) {
+                missingParameters.add(SUPER_CLIENT_ID);
+            }
+            if (OAuthUtils.isEmpty(request.getParameter(SUPER_CLIENT_SECRET))) {
+                missingParameters.add(SUPER_CLIENT_SECRET);
+            }
+
+            if (!missingParameters.isEmpty()) {
+                throw OAuthUtils.handleMissingParameters(missingParameters);
+            }
+        }
+    }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeTokenSuperValidator.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeTokenSuperValidator.java
index 454f0e5..c4a205f 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeTokenSuperValidator.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/RevokeTokenSuperValidator.java
@@ -13,8 +13,8 @@
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 
 /**
- * Defines required request parameters for
- * OAuth2RevokeTokenSuperRequest and validates the request method.
+ * Defines required parameters for revoking a refresh token via a
+ * super client
  * 
  * @author margaretha
  *
@@ -26,7 +26,7 @@
     public static final String SUPER_CLIENT_SECRET = "super_client_secret";
 
     public RevokeTokenSuperValidator () {
-        requiredParams.add(OAuth.OAUTH_CLIENT_ID);
+        requiredParams.add("token");
         requiredParams.add(SUPER_CLIENT_ID);
         requiredParams.add(SUPER_CLIENT_SECRET);
 
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 ebc7252..47cc265 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
@@ -33,6 +33,7 @@
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
 import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
 import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
@@ -414,18 +415,20 @@
 
     private void revokeRefreshToken (RefreshToken refreshToken)
             throws KustvaktException {
-        refreshToken.setRevoked(true);
-        refreshDao.updateRefreshToken(refreshToken);
-
-        Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
-        for (AccessToken accessToken : accessTokenList) {
-            accessToken.setRevoked(true);
-            tokenDao.updateAccessToken(accessToken);
+        if (refreshToken != null){
+            refreshToken.setRevoked(true);
+            refreshDao.updateRefreshToken(refreshToken);
+    
+            Set<AccessToken> accessTokenList = refreshToken.getAccessTokens();
+            for (AccessToken accessToken : accessTokenList) {
+                accessToken.setRevoked(true);
+                tokenDao.updateAccessToken(accessToken);
+            }
         }
     }
 
-    public void revokeTokenViaSuperClient (String username,
-            OAuth2RevokeTokenSuperRequest revokeTokenRequest)
+    public void revokeAllClientTokensViaSuperClient (String username,
+            OAuth2RevokeAllTokenSuperRequest revokeTokenRequest)
             throws KustvaktException {
         String superClientId = revokeTokenRequest.getSuperClientId();
         String superClientSecret = revokeTokenRequest.getSuperClientSecret();
@@ -439,7 +442,7 @@
 
         String clientId = revokeTokenRequest.getClientId();
         List<RefreshToken> refreshTokens =
-                tokenDao.retrieveRefreshTokenByClientId(clientId);
+                refreshDao.retrieveRefreshTokenByClientId(clientId);
 
         for (RefreshToken r : refreshTokens) {
             if (r.getUserId().equals(username)){
@@ -448,6 +451,23 @@
         }
     }
     
+    public void revokeTokensViaSuperClient (String username,
+            OAuth2RevokeTokenSuperRequest revokeTokenRequest) throws KustvaktException {
+        String superClientId = revokeTokenRequest.getSuperClientId();
+        String superClientSecret = revokeTokenRequest.getSuperClientSecret();
+
+        OAuth2Client superClient = clientService
+                .authenticateClient(superClientId, superClientSecret);
+        if (!superClient.isSuper()) {
+            throw new KustvaktException(
+                    StatusCodes.CLIENT_AUTHENTICATION_FAILED);
+        }
+        
+        String token = revokeTokenRequest.getToken();
+        RefreshToken refreshToken = refreshDao.retrieveRefreshToken(token, username);
+        revokeRefreshToken(refreshToken);
+    }
+    
     public List<OAuth2RefreshTokenDto> listUserRefreshToken (String username, String clientId,
             String clientSecret) throws KustvaktException {
         
@@ -488,4 +508,6 @@
         }
         return dtoList;
     }
+
+   
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index a287ce4..6ba63fb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -31,6 +31,7 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2RefreshTokenDto;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2AuthorizationRequest;
+import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
 import de.ids_mannheim.korap.oauth2.oltu.service.OltuAuthorizationService;
@@ -258,33 +259,24 @@
         }
     }
 
-    /**
-     * Revokes all tokens of a client from a super client. This
-     * service is not part of the OAUTH2 specification. It requires
-     * user authentication via authorization header, and super client
-     * via URL-encoded form parameters.
-     * 
-     * @param request
-     * @param form
-     * @return
-     */
+    
     @POST
     @Path("revoke/super")
     @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
-    public Response revokeTokenViaSuperClient (
-            @Context SecurityContext context,
+    public Response revokeTokenViaSuperClient (@Context SecurityContext context,
             @Context HttpServletRequest request,
             MultivaluedMap<String, String> form) {
 
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
-        
+
         try {
             OAuth2RevokeTokenSuperRequest revokeTokenRequest =
                     new OAuth2RevokeTokenSuperRequest(
                             new FormRequestWrapper(request, form));
-            tokenService.revokeTokenViaSuperClient(username, revokeTokenRequest);
+            tokenService.revokeTokensViaSuperClient(username,
+                    revokeTokenRequest);
             return Response.ok("SUCCESS").build();
         }
         catch (OAuthSystemException e) {
@@ -298,6 +290,51 @@
         }
     }
     
+    /**
+     * Revokes all tokens of a client for the authenticated user from
+     * a super client. This service is not part of the OAUTH2
+     * specification. It requires user authentication via
+     * authorization header, and super client
+     * via URL-encoded form parameters.
+     * 
+     * @param request
+     * @param form
+     *            containing client_id, super_client_id,
+     *            super_client_secret
+     * @return 200 if token invalidation is successful or the given
+     *         token is invalid
+     */
+    @POST
+    @Path("revoke/super/all")
+    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response revokeAllClientTokensViaSuperClient (
+            @Context SecurityContext context,
+            @Context HttpServletRequest request,
+            MultivaluedMap<String, String> form) {
+
+        TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
+        String username = tokenContext.getUsername();
+
+        try {
+            OAuth2RevokeAllTokenSuperRequest revokeTokenRequest =
+                    new OAuth2RevokeAllTokenSuperRequest(
+                            new FormRequestWrapper(request, form));
+            tokenService.revokeAllClientTokensViaSuperClient(username,
+                    revokeTokenRequest);
+            return Response.ok("SUCCESS").build();
+        }
+        catch (OAuthSystemException e) {
+            throw responseHandler.throwit(e);
+        }
+        catch (OAuthProblemException e) {
+            throw responseHandler.throwit(e);
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
+
     @POST
     @Path("token/list")
     @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
@@ -307,18 +344,17 @@
             @Context SecurityContext context,
             @FormParam("client_id") String clientId,
             @FormParam("client_secret") String clientSecret) {
-        
+
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
-        
+
         try {
             return tokenService.listUserRefreshToken(username, clientId,
-                    clientSecret);            
+                    clientSecret);
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
-        
-        
+
     }
 }
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 03f4ce8..c80ab36 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
@@ -537,7 +537,7 @@
         testListAuthorizedClientWithRefreshTokenFromAnotherUser(userAuthHeader);
         
         // revoke client 1
-        testRevokeTokenViaSuperClient(publicClientId, userAuthHeader,
+        testRevokeAllTokenViaSuperClient(publicClientId, userAuthHeader,
                 accessToken, refreshToken);
         testRequestTokenWithRevokedRefreshToken(publicClientId, clientSecret,
                 refreshToken);
@@ -546,7 +546,7 @@
         node = JsonUtils.readTree(response.getEntity(String.class));
         accessToken = node.at("/access_token").asText();
         refreshToken = node.at("/refresh_token").asText();
-        testRevokeTokenViaSuperClient(confidentialClientId, userAuthHeader,
+        testRevokeAllTokenViaSuperClient(confidentialClientId, userAuthHeader,
                 accessToken, refreshToken);
     }
 
@@ -580,11 +580,11 @@
         String accessToken = node.at("/access_token").asText();
         String refreshToken = node.at("/refresh_token").asText();
 
-        testRevokeTokenViaSuperClient(publicClientId, aaaAuthHeader,
+        testRevokeAllTokenViaSuperClient(publicClientId, aaaAuthHeader,
                 accessToken, refreshToken);
     }
 
-    private void testRevokeTokenViaSuperClient (String clientId,
+    private void testRevokeAllTokenViaSuperClient (String clientId,
             String userAuthHeader, String accessToken, String refreshToken)
             throws KustvaktException {
         // check token before revoking
@@ -598,12 +598,14 @@
         form.add("super_client_secret", clientSecret);
 
         response = resource().path(API_VERSION).path("oauth2").path("revoke")
-                .path("super").header(Attributes.AUTHORIZATION, userAuthHeader)
+                .path("super").path("all")
+                .header(Attributes.AUTHORIZATION, userAuthHeader)
                 .header(HttpHeaders.CONTENT_TYPE,
                         ContentType.APPLICATION_FORM_URLENCODED)
                 .entity(form).post(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        assertEquals("SUCCESS", response.getEntity(String.class));
 
         response = searchWithAccessToken(accessToken);
         node = JsonUtils.readTree(response.getEntity(String.class));
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 6a08801..b15d1e7 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
@@ -664,6 +664,24 @@
                 .entity(form).post(ClientResponse.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        assertEquals("SUCCESS", response.getEntity(String.class));
+    }
+    
+    private void testRevokeTokenViaSuperClient (String token, String userAuthHeader) {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("token", token);
+        form.add("super_client_id", superClientId);
+        form.add("super_client_secret", clientSecret);
+
+        ClientResponse response = resource().path(API_VERSION)
+                .path("oauth2").path("revoke").path("super")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .header(Attributes.AUTHORIZATION, userAuthHeader)
+                .entity(form).post(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        assertEquals("SUCCESS", response.getEntity(String.class));
     }
     
     @Test
@@ -707,10 +725,12 @@
                 publicClientId, "", code);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
+        // another user
+        String darlaAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("darla", "pwd");
         // client 1
         code = requestAuthorizationCode(publicClientId, clientSecret,
-                null, HttpAuthorizationHandler
-                .createBasicAuthorizationHeaderValue("darla", "pwd"));
+                null, darlaAuthHeader);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         response = requestTokenWithAuthorizationCodeAndForm(
                 publicClientId, "", code);
@@ -718,6 +738,7 @@
         node = JsonUtils.readTree(response.getEntity(String.class));
         String refreshToken5 = node.at("/refresh_token").asText();
         
+        // list
         node = requestRefreshTokenList(userAuthHeader);
         assertEquals(3, node.size());
         
@@ -731,10 +752,19 @@
         node = requestRefreshTokenList(userAuthHeader);
         assertEquals(1, node.size());
         
-        testRevokeToken(node.at("/0/token").asText(), publicClientId, null,
-                REFRESH_TOKEN_TYPE);
-        testRevokeToken(refreshToken5, publicClientId, null,
-                REFRESH_TOKEN_TYPE);
+        testRevokeTokenViaSuperClient(node.at("/0/token").asText(), userAuthHeader);
+        node = requestRefreshTokenList(userAuthHeader);
+        assertEquals(0, node.size());
+        
+        // try revoking a token belonging to another user
+        // should not return any errors
+        testRevokeTokenViaSuperClient(refreshToken5, userAuthHeader);
+        node = requestRefreshTokenList(darlaAuthHeader);
+        assertEquals(1, node.size());
+        
+        testRevokeTokenViaSuperClient(refreshToken5, darlaAuthHeader);
+        node = requestRefreshTokenList(darlaAuthHeader);
+        assertEquals(0, node.size());
     }
     
     private JsonNode requestRefreshTokenList (String userAuthHeader)