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

Change-Id: Ice92e3759678eac4d2322ff65a0997a003de357c
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")) {