Updated OAuth2 token list with token type and user clientId parameters.

Change-Id: Id7171fa09913cb2ab8bcefd7bce3003388ce93d4
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 b82819d..f3cc60a 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
@@ -183,4 +183,32 @@
         return q.getResultList();
     }
 
+    public List<AccessToken> retrieveAccessTokenByUser (String username, String clientId)
+            throws KustvaktException {
+        ParameterChecker.checkStringValue(username, "username");
+
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<AccessToken> query =
+                builder.createQuery(AccessToken.class);
+
+        Root<AccessToken> root = query.from(AccessToken.class);
+        root.fetch(AccessToken_.client);
+        Predicate condition = builder.and(
+                builder.equal(root.get(AccessToken_.userId), username),
+                builder.equal(root.get(AccessToken_.isRevoked), false),
+                builder.greaterThan(
+                        root.<ZonedDateTime> get(AccessToken_.expiryDate),
+                        ZonedDateTime
+                                .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))));
+        if (clientId != null && !clientId.isEmpty()) {
+            OAuth2Client client = clientDao.retrieveClientById(clientId);
+            condition = builder.and(condition,
+                    builder.equal(root.get(AccessToken_.client), client));
+        }
+            
+        query.select(root);
+        query.where(condition);
+        TypedQuery<AccessToken> 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 0bcc54d..585d8b6 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
@@ -143,7 +143,8 @@
         return q.getResultList();
     }
 
-    public List<RefreshToken> retrieveRefreshTokenByUser (String username)
+    public List<RefreshToken> retrieveRefreshTokenByUser (String username,
+            String clientId)
             throws KustvaktException {
         ParameterChecker.checkStringValue(username, "username");
 
@@ -160,6 +161,12 @@
                         root.<ZonedDateTime> get(RefreshToken_.expiryDate),
                         ZonedDateTime
                                 .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))));
+        if (clientId != null && !clientId.isEmpty()) {
+            OAuth2Client client = clientDao.retrieveClientById(clientId);
+            condition = builder.and(condition,
+                    builder.equal(root.get(RefreshToken_.client), client));
+        }
+        
         query.select(root);
         query.where(condition);
         TypedQuery<RefreshToken> q = entityManager.createQuery(query);
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 abab7ce..d066063 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
@@ -104,7 +104,7 @@
      * credentials.
      * 
      * TODO: should create a new refresh token when the old refresh
-     * token is used
+     * token is used (DONE)
      * 
      * @param refreshTokenStr
      * @param scopes
@@ -322,9 +322,9 @@
      * Base64.
      * 
      * <br /><br />
-     * Additionally, a refresh token is issued. It can be used to
-     * request a new access token without requiring user
-     * reauthentication.
+     * Additionally, a refresh token is issued for confidential clients. 
+     * It can be used to request a new access token without requiring user
+     * re-authentication.
      * 
      * @param scopes
      *            a set of access token scopes in String
@@ -512,17 +512,17 @@
         }
     }
     
-    public List<OAuth2TokenDto> listUserRefreshToken (String username, String clientId,
-            String clientSecret) throws KustvaktException {
+    public List<OAuth2TokenDto> listUserRefreshToken (String username, String superClientId,
+            String superClientSecret, String clientId) throws KustvaktException {
         
-        OAuth2Client client = clientService.authenticateClient(clientId, clientSecret);
+        OAuth2Client client = clientService.authenticateClient(superClientId, superClientSecret);
         if (!client.isSuper()) {
             throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
                     "Only super client is allowed.",
                     OAuth2Error.UNAUTHORIZED_CLIENT);
         }
 
-        List<RefreshToken> tokens = refreshDao.retrieveRefreshTokenByUser(username);
+        List<RefreshToken> tokens = refreshDao.retrieveRefreshTokenByUser(username, clientId);
         List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
         for (RefreshToken t : tokens){
             OAuth2Client tokenClient = t.getClient();
@@ -552,6 +552,48 @@
         }
         return dtoList;
     }
+    
+    public List<OAuth2TokenDto> listUserAccessToken (String username, String superClientId,
+            String superClientSecret, String clientId) throws KustvaktException {
+        
+        OAuth2Client superClient = clientService.authenticateClient(superClientId, superClientSecret);
+        if (!superClient.isSuper()) {
+            throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+                    "Only super client is allowed.",
+                    OAuth2Error.UNAUTHORIZED_CLIENT);
+        }
+
+        List<AccessToken> tokens =
+                tokenDao.retrieveAccessTokenByUser(username, clientId);
+        List<OAuth2TokenDto> dtoList = new ArrayList<>(tokens.size());
+        for (AccessToken t : tokens){
+            OAuth2Client tokenClient = t.getClient();
+            if (tokenClient.getId().equals(superClient.getId())){
+                continue;
+            }
+            OAuth2TokenDto dto = new OAuth2TokenDto();
+            dto.setClientId(tokenClient.getId());
+            dto.setClientName(tokenClient.getName());
+            dto.setClientUrl(tokenClient.getUrl());
+            dto.setClientDescription(tokenClient.getDescription());
+            
+            DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
+            dto.setCreatedDate(t.getCreatedDate().format(f));
+            dto.setExpiryDate(t.getExpiryDate().format(f));
+            dto.setUserAuthenticationTime(
+                    t.getUserAuthenticationTime().format(f));
+            dto.setToken(t.getToken());
+            
+            Set<AccessScope> accessScopes = t.getScopes();
+            Set<String> scopes = new HashSet<>(accessScopes.size());
+            for (AccessScope s : accessScopes){
+                scopes.add(s.getId().toString());
+            }
+            dto.setScopes(scopes);
+            dtoList.add(dto);
+        }
+        return dtoList;
+    }
 
     public String clearAccessTokenCache (String adminToken, String accessToken,
             ServletContext context) throws KustvaktException {
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 17fb0a4..e043837 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
@@ -30,6 +30,8 @@
 
 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.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.dto.OAuth2TokenDto;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2AuthorizationRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeAllTokenSuperRequest;
@@ -130,13 +132,20 @@
      * Grants a client an access token, namely a string used in
      * authenticated requests representing user authorization for
      * the client to access user resources. An additional refresh
-     * token strictly associated to the access token is also granted.
+     * token strictly associated to the access token is also granted
+     * for confidential clients. Both public and confidential clients
+     * may issue multiple access tokens.
      * 
      * <br /><br />
      * 
-     * Clients may request refresh access token using this endpoint.
-     * This request will grants a new access token. The refresh token
-     * is not changed and can be used until it expires.
+     * Confidential clients may request refresh access token using
+     * this endpoint. This request will grant a new access token.
+     * 
+     * Usually the given refresh token is not changed and can be used
+     * until it expires. However, currently there is a limitation of
+     * one access token per one refresh token. Thus, the given refresh
+     * token will be revoked, and a new access token and a new refresh
+     * token will be returned.
      * 
      * <br /><br />
      * 
@@ -260,7 +269,6 @@
         }
     }
 
-    
     @POST
     @Path("revoke/super")
     @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
@@ -290,7 +298,7 @@
             throw responseHandler.throwit(e);
         }
     }
-    
+
     /**
      * Revokes all tokens of a client for the authenticated user from
      * a super client. This service is not part of the OAUTH2
@@ -343,22 +351,35 @@
     @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
     public List<OAuth2TokenDto> listUserRefreshToken (
             @Context SecurityContext context,
-            @FormParam("client_id") String clientId,
-            @FormParam("client_secret") String clientSecret) {
+            @FormParam("super_client_id") String superClientId,
+            @FormParam("super_client_secret") String superClientSecret,
+            @FormParam("client_id") String clientId, // optional
+            @FormParam("token_type") String tokenType) {
 
         TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
         String username = tokenContext.getUsername();
 
         try {
-            return tokenService.listUserRefreshToken(username, clientId,
-                    clientSecret);
+            if (tokenType.equals("access_token")) {
+                return tokenService.listUserAccessToken(username, superClientId,
+                        superClientSecret, clientId);
+            }
+            else if (tokenType.equals("refresh_token")) {
+                return tokenService.listUserRefreshToken(username,
+                        superClientId, superClientSecret, clientId);
+            }
+            else {
+                throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
+                        "Missing token_type parameter value",
+                        OAuth2Error.INVALID_REQUEST);
+            }
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
         }
 
     }
-    
+
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Path("token/clear")
@@ -367,8 +388,8 @@
             @FormParam("access_token") String accessToken,
             @Context ServletContext context) {
         try {
-            String response = tokenService.clearAccessTokenCache(adminToken, accessToken,
-                    context);
+            String response = tokenService.clearAccessTokenCache(adminToken,
+                    accessToken, context);
             return Response.ok(response).build();
         }
         catch (KustvaktException e) {