Added revocation of access tokens and authorization code when
deregistering clients, and implemented reset client secret.

Change-Id: Ice92e3759678eac4d2322ff65a0997a003de357c
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
index e905192..dfe6443 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
@@ -1,5 +1,8 @@
 package de.ids_mannheim.korap.config;
 
+import java.io.InputStream;
+import java.util.Map;
+
 import de.ids_mannheim.korap.utils.ServiceInfo;
 import de.ids_mannheim.korap.utils.StringUtils;
 import net.sf.ehcache.Cache;
@@ -9,8 +12,6 @@
 import net.sf.ehcache.config.PersistenceConfiguration;
 import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
 
-import java.io.InputStream;
-
 /**
  * @author hanl
  * @date 03/02/2016
@@ -105,4 +106,9 @@
     private String createKey(String input) {
         return StringUtils.toSHAHash(this.prefix+ "@" + input);
     }
+    
+    public Map<Object, Element> getAllCacheElements () {
+        Cache cache = getCache(name);
+        return cache.getAll(cache.getKeysWithExpiryCheck());
+    }
 }
diff --git a/full/Changes b/full/Changes
index bba0520..ed71a4d 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,28 +1,46 @@
-version 0.61.0
-03/08/2018
-	- Added VC referencing tests (margaretha)
-	- Implemented loading and caching named VCs (margaretha)
-	- Implemented OAuth2 revoke token (margaretha)
-	- Updated OAuth2 refresh token implementation (margaretha)
+# version 0.61.0
 
-version 0.60.5
 02/08/2018
+	- Added VC referencing tests (margaretha)	
+	- Implemented loading and caching named VCs (margaretha)
+03/08/2018	
+    - Implemented OAuth2 revoke token (margaretha)
+	- Updated OAuth2 refresh token implementation (margaretha)
+14/08/2018	
+    - Disallow OAuth2 access token (type Bearer) usage for authentication 
+      in OAuth2 controllers (margaretha)
+	- Implemented revoke all OAuth2 access tokens and authorization codes of client  
+	  users when deregistering/deleting a client (margaretha)
+	- Fixed update OAuth2 access token (margaretha)
+	- Implemented reset client secret (margaretha)
+	  
+
+# version 0.60.5
+
+09/07/2018
 	- Added service layer to the search controller (margaretha)
 	- Added OAuth2 scope checking in search and VC controllers (margaretha)
 	- Added handling OAuth2 bearer token for VC access and User group controllers (margaretha)
 	- Added default scope to password grant (margaretha)
+10/07/2018
 	- Made createBasicAuthorizationHeaderValue static (margaretha)
 	- Added store access token in openID token service (margaretha)
 	- Fixed empty scope in openID authorization and token service (margaretha)
 	- Implemented storing authorization code in cache (margaretha)
+11/07/2018	
 	- Fixed authentication time in authentication controller (margaretha)
 	- Added OAuth2 access token tests (margaretha)
+12/07/2018	
 	- Updated maven surefire setting for faster test suite runtime (margaretha)
 	- Implemented refreshing OAuth2 access token (margaretha)
+26/07/2018	
 	- Fixed issue #27 (margaretha)
+02/08/2018	
 	- Fixed clientId encoding in OAuth2ClientControllerTest (margaretha)
+
 	
-version 0.60.4
+# version 0.60.4
+
 05/07/2018
     - implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
     - enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
@@ -49,7 +67,8 @@
     - added OAuth2 token request with client authentication via Authorization header (margaretha)
     - added port checking in test suite (margaretha)
     
-version 0.60.3
+# version 0.60.3
+
 06/06/2018
     - improved user authentication by using authentication filter for authorization code request (margaretha)
     - limited client authentication to client id checking in authorization code request (margaretha)
@@ -60,7 +79,8 @@
     - added database tables for MySQL (margaretha)
     - updated JWT library and related codes (margaretha)    
     
-version 0.60.2
+# version 0.60.2
+
 03/05/2018
     - implemented OAuth2 client registration (margaretha)
     - implemented OAuth2 client authentication (margaretha)
@@ -84,7 +104,8 @@
     - added access scopes handling (margaretha)
     - added tests about request token with authorization code (margaretha)
     
-version 0.60.1
+# version 0.60.1
+
 28/03/2018
     - added admin-related SQL codes (margaretha)
     - updated AdminDao (margaretha)
@@ -106,7 +127,8 @@
     - added member role removal after deleting members (margaretha)
     - added add and delete member role controllers (margaretha)
     
-version 0.60
+# version 0.60
+
 14/03/2018
     - set up mail settings using localhost port 25 (margaretha)
     - added mail template in kustvakt configuration (margaretha)
@@ -122,7 +144,8 @@
     - fixed member invitation to join deleted group (margaretha)
     - added checking deleted group (margaretha)
 
-version 0.59.10    
+# version 0.59.10
+    
 20/02/2018 
     - added sort VC by id (margaretha)
     - added test cases regarding VC sharing (margaretha)
@@ -145,7 +168,8 @@
     - fixed unrecognized application/json (margaretha)
     - fixed and updated velocity template (margaretha)
     
-version 0.59.9 
+# version 0.59.9 
+
 19/01/2018
     - restructured basic authentication (margaretha)
     - fixed AuthenticationException to include authentication scheme (margaretha)
@@ -172,7 +196,8 @@
     - removed PredefinedUserGroup.ALL and related codes (margaretha)
     - implemented search for published VC (margaretha)
     
-version 0.59.8 
+# version 0.59.8
+ 
 21/09/2017
     - restructured statistics service (margaretha)
     - removed deprecated loader codes and tests (margaretha)
@@ -191,7 +216,8 @@
     - implemented create/store VC (margaretha)
     - fixed collection rewrite bug regarding availability with operation or (margaretha)    
 
-version 0.59.7
+# version 0.59.7
+
 13/10/2016
     - MOD: updated search to use new siglen (diewald)
     - MOD: fixed matchinfo retrieval in light service (diewald)
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 67503e1..5564ee7 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
@@ -74,6 +74,18 @@
         entityManager.persist(accessToken);
     }
 
+    public AccessToken updateAccessToken (AccessToken accessToken)
+            throws KustvaktException {
+        ParameterChecker.checkObjectValue(accessToken, "access_token");
+        AccessToken cachedToken =
+                (AccessToken) this.getCacheValue(accessToken.getToken());
+        if (cachedToken != null) {
+            this.removeCacheEntry(accessToken.getToken());
+        }
+
+        accessToken = entityManager.merge(accessToken);
+        return accessToken;
+    }
 
     public AccessToken retrieveAccessToken (String accessToken)
             throws KustvaktException {
@@ -121,19 +133,6 @@
         return q.getResultList();
     }
 
-    public AccessToken updateAccessToken (AccessToken accessToken)
-            throws KustvaktException {
-        ParameterChecker.checkObjectValue(accessToken, "access_token");
-        AccessToken cachedToken =
-                (AccessToken) this.getCacheValue(accessToken.getId());
-        if (cachedToken != null) {
-            this.removeCacheEntry(cachedToken);
-        }
-
-        accessToken = entityManager.merge(accessToken);
-        return accessToken;
-    }
-
     public AccessToken retrieveAccessTokenByAnynomousToken (String token)
             throws KustvaktException {
         ParameterChecker.checkObjectValue(token, "token");
@@ -165,4 +164,16 @@
                     "Access token is not found", OAuth2Error.INVALID_TOKEN);
         }
     }
+
+    @SuppressWarnings("unchecked")
+    public List<AccessToken> retrieveAccessTokenByClientId (String clientId) {
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<AccessToken> query =
+                builder.createQuery(AccessToken.class);
+        Root<AccessToken> root = query.from(AccessToken.class);
+        query.select(root);
+        query.where(builder.equal(root.get(AccessToken_.clientId), clientId));
+        Query q = entityManager.createQuery(query);
+        return q.getResultList();
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
index eed72d4..af83e16 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
@@ -1,6 +1,9 @@
 package de.ids_mannheim.korap.oauth2.dao;
 
 import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import de.ids_mannheim.korap.config.KustvaktCacheable;
@@ -11,6 +14,7 @@
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.interfaces.AuthorizationDaoInterface;
 import de.ids_mannheim.korap.utils.ParameterChecker;
+import net.sf.ehcache.Element;
 
 public class AuthorizationCacheDao extends KustvaktCacheable
         implements AuthorizationDaoInterface {
@@ -69,4 +73,19 @@
         return auth;
     }
 
+    @Override
+    public List<Authorization> retrieveAuthorizationsByClientId (
+            String clientId) {
+        List<Authorization> authList = new ArrayList<>();
+        
+        Map<Object, Element> map = getAllCacheElements();
+        for (Object key : map.keySet()){
+            Authorization auth =  (Authorization) map.get(key).getObjectValue();
+            if (auth.getClientId().equals(clientId)){
+                authList.add(auth);
+            }
+        }
+        return authList;
+    }
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
index c62b130..ff82d9c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.oauth2.dao;
 
 import java.time.ZonedDateTime;
+import java.util.List;
 import java.util.Set;
 
 import javax.persistence.EntityManager;
@@ -25,14 +26,15 @@
 
 @Transactional
 @Repository
-public class AuthorizationDao implements AuthorizationDaoInterface{
+public class AuthorizationDao implements AuthorizationDaoInterface {
 
     @PersistenceContext
     private EntityManager entityManager;
 
     public Authorization storeAuthorizationCode (String clientId, String userId,
             String code, Set<AccessScope> scopes, String redirectURI,
-            ZonedDateTime authenticationTime, String nonce) throws KustvaktException {
+            ZonedDateTime authenticationTime, String nonce)
+            throws KustvaktException {
         ParameterChecker.checkStringValue(clientId, "client_id");
         ParameterChecker.checkStringValue(userId, "userId");
         ParameterChecker.checkStringValue(code, "authorization code");
@@ -85,4 +87,21 @@
         authorization = entityManager.merge(authorization);
         return authorization;
     }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<Authorization> retrieveAuthorizationsByClientId (String clientId) {
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<Authorization> query =
+                builder.createQuery(Authorization.class);
+        Root<Authorization> root = query.from(Authorization.class);
+
+        Predicate restrictions =
+                builder.equal(root.get(Authorization_.clientId), clientId);
+
+        query.select(root);
+        query.where(restrictions);
+        Query q = entityManager.createQuery(query);
+        return q.getResultList();
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
index 78c438c..2b8a2eb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
@@ -84,11 +84,17 @@
         }
     }
 
-    public void deregisterClient (OAuth2Client client) {
+    public void deregisterClient (OAuth2Client client) throws KustvaktException {
+        ParameterChecker.checkObjectValue(client, "client");
         if (!entityManager.contains(client)) {
             client = entityManager.merge(client);
         }
         entityManager.remove(client);
     }
 
+    public void updateClient (OAuth2Client client) throws KustvaktException {
+        ParameterChecker.checkObjectValue(client, "client");
+        client = entityManager.merge(client);
+    }
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
index f9c7280..8dcde58 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.oauth2.interfaces;
 
 import java.time.ZonedDateTime;
+import java.util.List;
 import java.util.Set;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -18,4 +19,6 @@
     
     public Authorization updateAuthorization (Authorization authorization)
             throws KustvaktException;
+
+    public List<Authorization> retrieveAuthorizationsByClientId (String clientId);
 }
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 8b3a639..53027f4 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
@@ -5,6 +5,7 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.sql.SQLException;
+import java.util.List;
 
 import org.apache.commons.validator.routines.UrlValidator;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -19,8 +20,12 @@
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
 import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
 import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.interfaces.AuthorizationDaoInterface;
 import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
 
 /**
@@ -46,6 +51,10 @@
     @Autowired
     private OAuth2ClientDao clientDao;
     @Autowired
+    private AccessTokenDao tokenDao;
+    @Autowired
+    private AuthorizationDaoInterface authorizationDao;
+    @Autowired
     private AdminDao adminDao;
     @Autowired
     private UrlValidator redirectURIValidator;
@@ -172,7 +181,52 @@
 
         if (adminDao.isAdmin(username)
                 || client.getRegisteredBy().equals(username)) {
+
             clientDao.deregisterClient(client);
+
+            // revoke all related authorization tokens
+            List<Authorization> authList = authorizationDao
+                    .retrieveAuthorizationsByClientId(clientId);
+            for (Authorization authorization : authList){
+                authorization.setRevoked(true);
+                authorizationDao.updateAuthorization(authorization);
+            }
+            
+            // revoke all related access tokens
+            List<AccessToken> tokens =
+                    tokenDao.retrieveAccessTokenByClientId(clientId);
+            for (AccessToken token : tokens) {
+                token.setRevoked(true);
+                token.setRefreshTokenRevoked(true);
+                tokenDao.updateAccessToken(token);
+            }
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + username, username);
+        }
+    }
+
+    public OAuth2ClientDto resetSecret (String clientId, String clientSecret,
+            String username) throws KustvaktException {
+
+        OAuth2Client client = authenticateClient(clientId, clientSecret);
+        if (!client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+            throw new KustvaktException(
+                    StatusCodes.NOT_ALLOWED,
+                    "Operation is not allowed for public clients",
+                    OAuth2Error.INVALID_REQUEST);
+        }
+        if (adminDao.isAdmin(username)
+                || client.getRegisteredBy().equals(username)) {
+
+            String secret = codeGenerator.createRandomCode();
+            String secretHashcode = encryption.secureHash(secret,
+                    config.getPasscodeSaltField());
+
+            client.setSecret(secretHashcode);
+            clientDao.updateClient(client);
+            return new OAuth2ClientDto(clientId, secret);
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
@@ -197,15 +251,7 @@
 
     public void authenticateClient (OAuth2Client client, String clientSecret)
             throws KustvaktException {
-        if (clientSecret == null) {
-            if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
-                throw new KustvaktException(
-                        StatusCodes.CLIENT_AUTHENTICATION_FAILED,
-                        "Missing parameters: client_secret",
-                        OAuth2Error.INVALID_REQUEST);
-            }
-        }
-        else if (clientSecret.isEmpty()) {
+        if (clientSecret == null || clientSecret.isEmpty()) {
             if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
                 throw new KustvaktException(
                         StatusCodes.CLIENT_AUTHENTICATION_FAILED,
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 e78aa2a..61f8237 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
@@ -24,7 +24,6 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 
-import com.sun.jersey.api.client.ClientResponse.Status;
 import com.sun.jersey.spi.container.ResourceFilters;
 
 import de.ids_mannheim.korap.exceptions.KustvaktException;
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 61295e7..293685e 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
@@ -29,8 +29,8 @@
 
 /**
  * Defines controllers for OAuth2 clients, namely applications
- * attempting
- * to access users' resources.
+ * performing actions such as searching and retrieving match
+ * information on behalf of users.
  * 
  * @author margaretha
  *
@@ -117,4 +117,35 @@
             throw responseHandler.throwit(e);
         }
     }
+
+    /**
+     * Resets client secret of the given client. This controller
+     * requires client owner and client authentication. Only
+     * confidential clients are issued client secrets.
+     * 
+     * @param securityContext
+     * @param clientId
+     * @param clientSecret
+     * @return a new client secret
+     */
+    @POST
+    @Path("reset")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+    public OAuth2ClientDto resetClientSecret (
+            @Context SecurityContext securityContext,
+            @FormParam("client_id") String clientId,
+            @FormParam("client_secret") String clientSecret) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            return clientService.resetSecret(clientId, clientSecret,
+                    context.getUsername());
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
index 88603cc..d0a6cf6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
@@ -75,7 +75,8 @@
                         context = authenticationManager.getTokenContext(
                                 TokenType.BEARER, authData.getToken(), host,
                                 ua);
-                        if (request.getPath().startsWith("vc/access")
+                        if (request.getPath().startsWith("oauth2")
+                                || request.getPath().startsWith("vc/access")
                                 || request.getPath().startsWith("vc/delete")
                                 || request.getPath().startsWith("group")
                                 || request.getPath().startsWith("user")) {
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 b5f89a5..ffecbb3 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -4,6 +4,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import java.net.URI;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -12,6 +13,8 @@
 
 import org.apache.http.entity.ContentType;
 import org.junit.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
@@ -52,7 +55,7 @@
         }
     }
 
-    private ClientResponse testRegisterConfidentialClient ()
+    private ClientResponse registerConfidentialClient ()
             throws KustvaktException {
 
         OAuth2ClientJson json = new OAuth2ClientJson();
@@ -71,8 +74,8 @@
     }
 
     @Test
-    public void testRegisterClientNonUniqueURL () throws KustvaktException {
-        ClientResponse response = testRegisterConfidentialClient();
+    public void testRegisterConfidentialClient () throws KustvaktException {
+        ClientResponse response = registerConfidentialClient();
         String entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         JsonNode node = JsonUtils.readTree(entity);
@@ -81,14 +84,22 @@
         assertNotNull(clientId);
         assertNotNull(clientSecret);
 
-        response = testRegisterConfidentialClient();
-        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        node = JsonUtils.readTree(response.getEntity(String.class));
-        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        testRegisterClientNonUniqueURL();
+
+        String newclientSecret =
+                testResetConfidentialClientSecret(clientId, clientSecret);
 
         testDeregisterConfidentialClientMissingSecret(clientId);
-        testDeregisterClientIncorrectCredentials(clientId);
-        testDeregisterConfidentialClient(clientId, clientSecret);
+        testDeregisterClientIncorrectCredentials(clientId, clientSecret);
+        testDeregisterConfidentialClient(clientId, newclientSecret);
+    }
+
+
+    private void testRegisterClientNonUniqueURL () throws KustvaktException {
+        ClientResponse response = registerConfidentialClient();
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
     }
 
     @Test
@@ -122,29 +133,6 @@
     }
 
     @Test
-    public void testRegisterNativeClient () throws UniformInterfaceException,
-            ClientHandlerException, KustvaktException {
-        OAuth2ClientJson json = new OAuth2ClientJson();
-        json.setName("NativeClient");
-        json.setType(OAuth2ClientType.PUBLIC);
-        json.setUrl("http://korap.ids-mannheim.de/native");
-        json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
-        json.setDescription("This is a native test client.");
-
-        ClientResponse response = resource().path("oauth2").path("client")
-                .path("register")
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue(username, "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
-                .entity(json).post(ClientResponse.class);
-
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
-        // EM: need to check native
-    }
-
-    @Test
     public void testRegisterDesktopApp () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         OAuth2ClientJson json = new OAuth2ClientJson();
@@ -166,6 +154,117 @@
         String clientId = node.at("/client_id").asText();
         assertNotNull(clientId);
         assertTrue(node.at("/client_secret").isMissingNode());
+
+        testResetPublicClientSecret(clientId);
+    }
+
+    @Test
+    public void testRegisterNativeClient () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        OAuth2ClientJson json = new OAuth2ClientJson();
+        json.setName("NativeClient");
+        json.setType(OAuth2ClientType.PUBLIC);
+        json.setUrl("http://korap.ids-mannheim.de/native");
+        json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
+        json.setDescription("This is a native test client.");
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("register")
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .entity(json).post(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(entity);
+        String clientId = node.at("/client_id").asText();
+
+        // EM: need to check native
+
+        testAccessTokenAfterDeregistration(clientId);
+    }
+
+    private void testAccessTokenAfterDeregistration (String clientId)
+            throws KustvaktException {
+        String code = requestAuthorizationCode(clientId, "");
+        ClientResponse response = requestAccessToken(code, clientId, "");
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        String accessToken = node.at("/access_token").asText();
+
+        response = searchWithAccessToken(accessToken);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        code = requestAuthorizationCode(clientId, "");
+        testDeregisterPublicClient(clientId);
+
+        response = requestAccessToken(code, clientId, "");
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        node = JsonUtils.readTree(response.getEntity(String.class));
+        assertEquals(OAuth2Error.INVALID_CLIENT.toString(),
+                node.at("/error").asText());
+
+        response = searchWithAccessToken(accessToken);
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        node = JsonUtils.readTree(response.getEntity(String.class));
+        assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+                node.at("/errors/0/0").asInt());
+        assertEquals("Access token has been revoked",
+                node.at("/errors/0/1").asText());
+
+    }
+
+    private String requestAuthorizationCode (String clientId,
+            String clientSecret) throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("response_type", "code");
+        form.add("client_id", clientId);
+        form.add("client_secret", clientSecret);
+
+        ClientResponse response = resource().path("oauth2").path("authorize")
+                .header(Attributes.AUTHORIZATION,
+                        HttpAuthorizationHandler
+                                .createBasicAuthorizationHeaderValue("dory",
+                                        "password"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+                response.getStatus());
+        URI redirectUri = response.getLocation();
+        MultiValueMap<String, String> params = UriComponentsBuilder
+                .fromUri(redirectUri).build().getQueryParams();
+        return params.getFirst("code");
+    }
+
+    private ClientResponse requestAccessToken (String code, String clientId,
+            String clientSecret) throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("grant_type", "authorization_code");
+        form.add("client_id", clientId);
+        form.add("client_secret", clientSecret);
+        form.add("code", code);
+
+        ClientResponse response = resource().path("oauth2").path("token")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        return response;
+    }
+
+    private ClientResponse searchWithAccessToken (String accessToken) {
+        ClientResponse response = resource().path("search")
+                .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+                .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+
+        return response;
     }
 
     private void testDeregisterPublicClientMissingUserAuthentication (
@@ -247,12 +346,12 @@
                 node.at("/error_description").asText());
     }
 
-    private void testDeregisterClientIncorrectCredentials (String clientId)
-            throws UniformInterfaceException, ClientHandlerException,
-            KustvaktException {
+    private void testDeregisterClientIncorrectCredentials (String clientId,
+            String clientSecret) throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
 
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
-        form.add("client_secret", "xxx");
+        form.add("client_secret", clientSecret);
 
         ClientResponse response = resource().path("oauth2").path("client")
                 .path("deregister").path(clientId)
@@ -272,4 +371,53 @@
 
         checkWWWAuthenticateHeader(response);
     }
+
+    private void testResetPublicClientSecret (String clientId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("reset")
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+        assertEquals("Operation is not allowed for public clients",
+                node.at("/error_description").asText());
+    }
+
+    private String testResetConfidentialClientSecret (String clientId,
+            String clientSecret) throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", clientId);
+        form.add("client_secret", clientSecret);
+
+        ClientResponse response = resource().path("oauth2").path("client")
+                .path("reset")
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(username, "pass"))
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(clientId, node.at("/client_id").asText());
+
+        String newClientSecret = node.at("/client_secret").asText();
+        assertTrue(!clientSecret.equals(newClientSecret));
+
+        return newClientSecret;
+    }
 }