Added a service to list active refresh tokens of a user.

Change-Id: I4f9508d9e12162bdf18fb2d61d3347000f29af0a


Change-Id: I4f9508d9e12162bdf18fb2d61d3347000f29af0a
diff --git a/full/Changes b/full/Changes
index e9e62c9..ae73b6f 100644
--- a/full/Changes
+++ b/full/Changes
@@ -22,6 +22,8 @@
 15/11/2019
    - Merged list authorized client and list registered client services
      (margaretha)
+21/11/2019
+   - Added a service to list active refresh tokens of a user (margaretha)    
 
 # version 0.62.1
 08/07/2019
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
index 650a118..3fd5722 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
@@ -100,7 +100,7 @@
 	 */
 	@Override
 	public TokenContext getTokenContext(TokenType type, String token, 
-	        String host, String useragent) throws KustvaktException {
+	        String host, String userAgent) throws KustvaktException {
 
 		AuthenticationIface provider = getProvider(type , null);
 
@@ -111,6 +111,8 @@
 		}
 		
 		TokenContext context = provider.getTokenContext(token);
+		context.setHostAddress(host);
+		context.setUserAgent(userAgent);
 		// if (!matchStatus(host, useragent, context))
 		// provider.removeUserSession(token);
 		return context;
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 87c7a6d..339f3cd 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
@@ -12,6 +12,7 @@
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Join;
+import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -108,27 +109,26 @@
         return token;
     }
 
-    // public List<RefreshToken> retrieveRefreshTokenByUser (String
-    // username)
-    // throws KustvaktException {
-    // 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_.isRevoked), false),
-    // builder.greaterThan(
-    // root.<ZonedDateTime> get(RefreshToken_.expiryDate),
-    // ZonedDateTime
-    // .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE)))
-    // );
-    // query.select(root);
-    // query.where(condition);
-    // TypedQuery<RefreshToken> q = entityManager.createQuery(query);
-    // return q.getResultList();
-    // }
+    public List<RefreshToken> retrieveRefreshTokenByUser (String username)
+            throws KustvaktException {
+        ParameterChecker.checkStringValue(username, "username");
+
+        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<RefreshToken> query =
+                builder.createQuery(RefreshToken.class);
+
+        Root<RefreshToken> root = query.from(RefreshToken.class);
+        root.fetch(RefreshToken_.client);
+        Predicate condition = builder.and(
+                builder.equal(root.get(RefreshToken_.userId), username),
+                builder.equal(root.get(RefreshToken_.isRevoked), false),
+                builder.greaterThan(
+                        root.<ZonedDateTime> get(RefreshToken_.expiryDate),
+                        ZonedDateTime
+                                .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))));
+        query.select(root);
+        query.where(condition);
+        TypedQuery<RefreshToken> q = entityManager.createQuery(query);
+        return q.getResultList();
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2RefreshTokenDto.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2RefreshTokenDto.java
new file mode 100644
index 0000000..8fac3ca
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dto/OAuth2RefreshTokenDto.java
@@ -0,0 +1,97 @@
+package de.ids_mannheim.korap.oauth2.dto;
+
+import java.util.Set;
+
+/**
+ * Describes OAuth2 refresh tokens
+ * 
+ * @author margaretha
+ *
+ */
+public class OAuth2RefreshTokenDto {
+
+    private String token;
+    private String createdDate;
+    private String expiryDate;
+    private String userAuthenticationTime;
+    private Set<String> scopes;
+
+    private String clientId;
+    private String clientName;
+    private String clientDescription;
+    private String clientUrl;
+
+    public String getToken () {
+        return token;
+    }
+
+    public void setToken (String token) {
+        this.token = token;
+    }
+
+    public String getClientId () {
+        return clientId;
+    }
+
+    public void setClientId (String clientId) {
+        this.clientId = clientId;
+    }
+
+    public String getClientName () {
+        return clientName;
+    }
+
+    public void setClientName (String clientName) {
+        this.clientName = clientName;
+    }
+
+    public String getClientDescription () {
+        return clientDescription;
+    }
+
+    public void setClientDescription (String clientDescription) {
+        this.clientDescription = clientDescription;
+    }
+
+    public String getClientUrl () {
+        return clientUrl;
+    }
+
+    public void setClientUrl (String clientUrl) {
+        this.clientUrl = clientUrl;
+    }
+
+    public String getCreatedDate () {
+        return createdDate;
+    }
+
+    public void setCreatedDate (String createdDate) {
+        this.createdDate = createdDate;
+    }
+
+    public String getExpiryDate () {
+        return expiryDate;
+    }
+
+    public void setExpiryDate (String expiryDate) {
+        this.expiryDate = expiryDate;
+    }
+
+    public String getUserAuthenticationTime () {
+        return userAuthenticationTime;
+    }
+
+    public void setUserAuthenticationTime (
+            String userAuthenticationTime) {
+        this.userAuthenticationTime = userAuthenticationTime;
+    }
+
+    public Set<String> getScopes () {
+        return scopes;
+    }
+
+    public void setScopes (Set<String> scopes) {
+        this.scopes = scopes;
+    }
+
+}
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 c18ba88..feafc87 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
@@ -2,6 +2,8 @@
 
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -25,6 +27,7 @@
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
 import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
 import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+import de.ids_mannheim.korap.oauth2.dto.OAuth2RefreshTokenDto;
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.AccessToken;
 import de.ids_mannheim.korap.oauth2.entity.Authorization;
@@ -32,6 +35,7 @@
 import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
 import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
 
 /** Implementation of token service using Apache Oltu.
@@ -49,6 +53,9 @@
     private AccessTokenDao tokenDao;
     @Autowired
     private RefreshTokenDao refreshDao;
+    
+    @Autowired
+    private OAuth2ClientService clientService;
 
     public OAuthResponse requestAccessToken (
             AbstractOAuthTokenRequest oAuthRequest)
@@ -438,4 +445,45 @@
             revokeRefreshToken(r);
         }
     }
+    
+    public List<OAuth2RefreshTokenDto> listUserRefreshToken (String username, String clientId,
+            String clientSecret) throws KustvaktException {
+        
+        OAuth2Client client = clientService.authenticateClient(clientId, clientSecret);
+        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<OAuth2RefreshTokenDto> dtoList = new ArrayList<>(tokens.size());
+        for (RefreshToken t : tokens){
+            OAuth2Client tokenClient = t.getClient();
+            if (tokenClient.getId().equals(client.getId())){
+                continue;
+            }
+            OAuth2RefreshTokenDto dto = new OAuth2RefreshTokenDto();
+            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;
+    }
 }
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 0cd6171..bbe1122 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
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.web.controller;
 
 import java.time.ZonedDateTime;
+import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -28,6 +29,7 @@
 
 import de.ids_mannheim.korap.constant.OAuth2Scope;
 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.OAuth2RevokeTokenRequest;
 import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
@@ -291,4 +293,28 @@
             throw responseHandler.throwit(e);
         }
     }
+    
+    @POST
+    @Path("token/list")
+    @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+    public List<OAuth2RefreshTokenDto> listUserRefreshToken (
+            @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);            
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+        
+        
+    }
 }
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 e1cd67b..6a08801 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
@@ -25,9 +25,9 @@
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
+import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
-import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
 import de.ids_mannheim.korap.oauth2.entity.AccessScope;
 import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
 import de.ids_mannheim.korap.utils.JsonUtils;
@@ -39,7 +39,9 @@
 public class OAuth2ControllerTest extends OAuth2TestBase {
 
     public String userAuthHeader;
-
+    public static String ACCESS_TOKEN_TYPE = "access_token";
+    public static String REFRESH_TOKEN_TYPE = "refresh_token";
+    
     public OAuth2ControllerTest () throws KustvaktException {
         userAuthHeader = HttpAuthorizationHandler
                 .createBasicAuthorizationHeaderValue("dory", "password");
@@ -181,7 +183,7 @@
         assertNotNull(node.at("/expires_in").asText());
 
         testRevokeToken(accessToken, publicClientId,null,
-                "access_token");
+                ACCESS_TOKEN_TYPE);
 
         testRequestRefreshTokenInvalidScope(publicClientId, refreshToken);
         testRequestRefreshTokenInvalidClient(refreshToken);
@@ -225,8 +227,9 @@
         
         String refreshToken = node.at("/refresh_token").asText();
         testRevokeToken(refreshToken, confidentialClientId,clientSecret,
-                "refresh_token");
-        testRequestTokenWithRevokedRefreshToken(confidentialClientId, clientSecret, refreshToken);
+                REFRESH_TOKEN_TYPE);
+        testRequestTokenWithRevokedRefreshToken(confidentialClientId,
+                clientSecret, refreshToken);
     }
 
     private void testRequestTokenWithUsedAuthorization (String code)
@@ -663,4 +666,93 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
     }
     
+    @Test
+    public void testListRefreshToken () throws KustvaktException {
+        String username = "gurgle";
+        String password = "pwd";
+        userAuthHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue(username, password);
+
+        // super client
+        ClientResponse response = requestTokenWithPassword(superClientId,
+                clientSecret, username, password);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+        String refreshToken1 = node.at("/refresh_token").asText();
+
+        // client 1
+        String code = requestAuthorizationCode(publicClientId, clientSecret,
+                null, userAuthHeader);
+        response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "",
+                code);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // client 2
+        code = requestAuthorizationCode(confidentialClientId, clientSecret,
+                null, userAuthHeader);
+        response = requestTokenWithAuthorizationCodeAndForm(
+                confidentialClientId, clientSecret, code);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // list
+        node = requestRefreshTokenList(userAuthHeader);
+        assertEquals(2, node.size());
+        assertEquals(publicClientId, node.at("/0/clientId").asText());
+        assertEquals(confidentialClientId, node.at("/1/clientId").asText());
+        
+        // client 1
+        code = requestAuthorizationCode(publicClientId, clientSecret,
+                null, userAuthHeader);
+        response = requestTokenWithAuthorizationCodeAndForm(
+                publicClientId, "", code);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // client 1
+        code = requestAuthorizationCode(publicClientId, clientSecret,
+                null, HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue("darla", "pwd"));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        response = requestTokenWithAuthorizationCodeAndForm(
+                publicClientId, "", code);
+        
+        node = JsonUtils.readTree(response.getEntity(String.class));
+        String refreshToken5 = node.at("/refresh_token").asText();
+        
+        node = requestRefreshTokenList(userAuthHeader);
+        assertEquals(3, node.size());
+        
+        testRevokeToken(refreshToken1, superClientId, clientSecret,
+                REFRESH_TOKEN_TYPE);
+        testRevokeToken(node.at("/0/token").asText(), publicClientId, null,
+                REFRESH_TOKEN_TYPE);
+        testRevokeToken(node.at("/1/token").asText(), confidentialClientId,
+                clientSecret, REFRESH_TOKEN_TYPE);
+        
+        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);
+    }
+    
+    private JsonNode requestRefreshTokenList (String userAuthHeader)
+            throws KustvaktException {
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        form.add("client_id", superClientId);
+        form.add("client_secret", clientSecret);
+
+        ClientResponse response = resource().path(API_VERSION).path("oauth2")
+                .path("token").path("list")
+                .header(Attributes.AUTHORIZATION, userAuthHeader)
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .entity(form).post(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        String entity = response.getEntity(String.class);
+        return JsonUtils.readTree(entity);
+    }
 }