Fixed scope check in token request with refresh token (close #556)

Change-Id: Ia1f9b6f4fff05eeb9b8517f963b28d4c6e8950c1
diff --git a/full/Changes b/full/Changes
index 5f94e14..6cbb312 100644
--- a/full/Changes
+++ b/full/Changes
@@ -5,6 +5,7 @@
 - Move oauth2 admin service path (closed #544) 
 - Excluded admin service to load-cache VC from the test suite
 - Fixed refresh token expiry test
+- Fixed scope check in OAuth2 token request with refresh token
 
 # version 0.69.2
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
index 5a8a498..99ee216 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessScope.java
@@ -63,4 +63,9 @@
 
         return false;
     }
+    
+@Override
+    public int hashCode () {
+        return this.getId().hashCode();
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index 88c572c..26fa82b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -39,8 +39,12 @@
     private String registeredBy;
     @Column(name = "registration_date", updatable = false)
     private ZonedDateTime registrationDate;
+    
+    // How long a refresh token for this client should be valid
+    // in seconds. Maximum 31536000 seconds equivalent to 1 year     
     @Column(name = "refresh_token_expiry")
     private int refreshTokenExpiry;
+    
     private String description;
     private String url;
 
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 30e1196..5b1fbc9 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
@@ -127,7 +127,7 @@
      * token is used (DONE)
      * 
      * @param refreshTokenStr
-     * @param scopes
+     * @param requestScopes
      * @param clientId
      * @param clientSecret
      * @return if successful, a new access token
@@ -135,7 +135,7 @@
      * @throws OAuthSystemException
      */
     private OAuthResponse requestAccessTokenWithRefreshToken (
-            String refreshTokenStr, Set<String> scopes, String clientId,
+            String refreshTokenStr, Set<String> requestScopes, String clientId,
             String clientSecret)
             throws KustvaktException, OAuthSystemException {
 
@@ -158,7 +158,7 @@
 
         if (!clientId.equals(refreshToken.getClient().getId())) {
             throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
-                    "Client " + clientId + "is not authorized",
+                    "Client " + clientId + " is not authorized",
                     OAuth2Error.INVALID_CLIENT);
         }
         else if (refreshToken.isRevoked()) {
@@ -172,19 +172,19 @@
                     "Refresh token is expired", OAuth2Error.INVALID_GRANT);
         }
 
-        Set<AccessScope> requestedScopes =
+        Set<AccessScope> tokenScopes =
                 new HashSet<>(refreshToken.getScopes());
-        if (scopes != null && !scopes.isEmpty()) {
-            requestedScopes =
-                    scopeService.verifyRefreshScope(scopes, requestedScopes);
-            scopes = scopeService
-                    .convertAccessScopesToStringSet(requestedScopes);
+        if (requestScopes != null && !requestScopes.isEmpty()) {
+            tokenScopes =
+                    scopeService.verifyRefreshScope(requestScopes, tokenScopes);
+            requestScopes = scopeService
+                    .convertAccessScopesToStringSet(tokenScopes);
         }
 
         // revoke the refresh token and all access tokens associated to it
         revokeRefreshToken(refreshTokenStr);
 
-        return createsAccessTokenResponse(scopes, requestedScopes, clientId,
+        return createsAccessTokenResponse(requestScopes, tokenScopes, clientId,
                 refreshToken.getUserId(),
                 refreshToken.getUserAuthenticationTime(), oAuth2Client);
 
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 4621bef..c2e21fd 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
@@ -461,11 +461,14 @@
 
         String entity = response.readEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
+        
         assertNotNull(node.at("/access_token").asText());
         assertEquals(TokenType.BEARER.toString(),
                 node.at("/token_type").asText());
         assertNotNull(node.at("/expires_in").asText());
-
+        assertEquals("all",node.at("/scope").asText());
+        
+        
         String refresh = node.at("/refresh_token").asText();
         RefreshToken refreshToken =
                 refreshTokenDao.retrieveRefreshToken(refresh);
@@ -475,6 +478,42 @@
         
         testRefreshTokenExpiry(refresh);
     }
+    
+    @Test
+    public void testRequestTokenPasswordGrantWithScope ()
+            throws KustvaktException {
+        
+        String scope ="match_info search";
+        
+        Form form = new Form();
+        form.param("grant_type", "password");
+        form.param("client_id", superClientId);
+        form.param("client_secret", clientSecret);
+        form.param("username", "dory");
+        form.param("password", "pwd");
+        form.param("scope",scope);
+        
+        Response response = requestToken(form);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        
+        assertNotNull(node.at("/access_token").asText());
+        assertEquals(TokenType.BEARER.toString(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+        
+        assertEquals(scope,node.at("/scope").asText());
+        
+        String refreshToken = node.at("/refresh_token").asText();
+        testRequestRefreshTokenWithUnauthorizedScope(superClientId, clientSecret,
+                refreshToken,"all");
+        
+        testRequestRefreshTokenWithScope(superClientId, clientSecret,
+                refreshToken,"search");
+    }
 
     @Test
     public void testRequestTokenPasswordGrantConfidentialNonSuper ()
@@ -760,6 +799,61 @@
                 newRefreshToken);
     }
 
+    
+    private void testRequestRefreshTokenWithUnauthorizedScope (String clientId,
+            String clientSecret, String refreshToken, String scope)
+            throws KustvaktException {
+        Form form = new Form();
+        form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+        form.param("client_id", clientId);
+        form.param("client_secret", clientSecret);
+        form.param("refresh_token", refreshToken);
+        form.param("scope", scope);
+
+        Response response = target().path(API_VERSION).path("oauth2")
+                .path("token").request()
+                .header(HttpHeaders.CONTENT_TYPE,
+                        ContentType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(form));
+
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(OAuth2Error.INVALID_SCOPE, node.at("/error").asText());
+    }
+    
+    private void testRequestRefreshTokenWithScope (String clientId, String clientSecret,
+            String refreshToken, String scope) throws KustvaktException {
+        Form form = new Form();
+        form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
+        form.param("client_id", clientId);
+        form.param("client_secret", clientSecret);
+        form.param("refresh_token", refreshToken);
+        form.param("scope",scope);
+
+        Response response =
+                target().path(API_VERSION).path("oauth2").path("token")
+                        .request()
+                        .header(HttpHeaders.CONTENT_TYPE,
+                                ContentType.APPLICATION_FORM_URLENCODED)
+                        .post(Entity.form(form));
+
+        String entity = response.readEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        JsonNode node = JsonUtils.readTree(entity);
+        assertNotNull(node.at("/access_token").asText());
+        
+        String newRefreshToken = node.at("/refresh_token").asText();
+        assertNotNull(newRefreshToken);
+        assertEquals(TokenType.BEARER.toString(),
+                node.at("/token_type").asText());
+        assertNotNull(node.at("/expires_in").asText());
+
+        assertTrue(!newRefreshToken.equals(refreshToken));
+        
+        assertEquals(scope,node.at("/scope").asText());
+    }
+    
     private void testRequestRefreshTokenInvalidClient (String refreshToken)
             throws KustvaktException {
         Form form = new Form();