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();