Added an admin API to delete expired/revoked access and refresh tokens.
Change-Id: Ie939a6aa26edf2747981943e5f40daa3485be461
diff --git a/full/Changes b/full/Changes
index 0a6469c..653149b 100644
--- a/full/Changes
+++ b/full/Changes
@@ -7,6 +7,8 @@
- Added more parameter checks and OAuth2Client web-service tests.
2022-03-17
- Updated admin filter by using admin token and role checks.
+2022-03-18
+ - Added an OAuth2 admin API to delete expired/revoked access and refresh tokens.
# version 0.65.1
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 6684a53..6d8dd43 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
@@ -57,7 +57,8 @@
Set<AccessScope> scopes, String userId, String clientId,
ZonedDateTime authenticationTime) throws KustvaktException {
ParameterChecker.checkStringValue(token, "access_token");
-// ParameterChecker.checkObjectValue(refreshToken, "refresh token");
+ // ParameterChecker.checkObjectValue(refreshToken, "refresh
+ // token");
ParameterChecker.checkObjectValue(scopes, "scopes");
// ParameterChecker.checkStringValue(userId, "username");
ParameterChecker.checkStringValue(clientId, "client_id");
@@ -69,7 +70,7 @@
ZonedDateTime expiry;
AccessToken accessToken = new AccessToken();
-
+
if (refreshToken != null) {
accessToken.setRefreshToken(refreshToken);
expiry = now.plusSeconds(config.getAccessTokenExpiry());
@@ -77,9 +78,9 @@
else {
expiry = now.plusSeconds(config.getAccessTokenLongExpiry());
}
-
+
OAuth2Client client = clientDao.retrieveClientById(clientId);
-
+
accessToken.setCreatedDate(now);
accessToken.setExpiryDate(expiry);
accessToken.setToken(token);
@@ -142,11 +143,11 @@
CriteriaQuery<AccessToken> query =
builder.createQuery(AccessToken.class);
Root<AccessToken> root = query.from(AccessToken.class);
-
+
Predicate condition = builder.and(
builder.equal(root.get(AccessToken_.userId), username),
builder.equal(root.get(AccessToken_.token), accessToken));
-
+
query.select(root);
query.where(condition);
Query q = entityManager.createQuery(query);
@@ -160,31 +161,31 @@
}
}
-
public List<AccessToken> retrieveAccessTokenByClientId (String clientId,
String username) throws KustvaktException {
ParameterChecker.checkStringValue(clientId, "client_id");
OAuth2Client client = clientDao.retrieveClientById(clientId);
-
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<AccessToken> query =
builder.createQuery(AccessToken.class);
Root<AccessToken> root = query.from(AccessToken.class);
-
- Predicate condition = builder.equal(root.get(AccessToken_.client), client);
- if (username != null && !username.isEmpty()){
+
+ Predicate condition =
+ builder.equal(root.get(AccessToken_.client), client);
+ if (username != null && !username.isEmpty()) {
condition = builder.and(condition,
builder.equal(root.get(AccessToken_.userId), username));
}
-
+
query.select(root);
query.where(condition);
TypedQuery<AccessToken> q = entityManager.createQuery(query);
return q.getResultList();
}
- public List<AccessToken> retrieveAccessTokenByUser (String username, String clientId)
- throws KustvaktException {
+ public List<AccessToken> retrieveAccessTokenByUser (String username,
+ String clientId) throws KustvaktException {
ParameterChecker.checkStringValue(username, "username");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
@@ -205,7 +206,31 @@
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();
+ }
+
+ public void deleteInvalidAccessTokens () {
+ List<AccessToken> invalidAccessTokens = retrieveInvalidAccessTokens();
+ invalidAccessTokens.forEach(token -> entityManager.remove(token));
+ }
+
+ public List<AccessToken> retrieveInvalidAccessTokens () {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<AccessToken> query =
+ builder.createQuery(AccessToken.class);
+
+ Root<AccessToken> root = query.from(AccessToken.class);
+ Predicate condition = builder.or(
+ builder.equal(root.get(AccessToken_.isRevoked), true),
+ builder.lessThan(
+ root.<ZonedDateTime> get(AccessToken_.expiryDate),
+ ZonedDateTime
+ .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))));
+
query.select(root);
query.where(condition);
TypedQuery<AccessToken> q = entityManager.createQuery(query);
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 2367fe3..7ff6138 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
@@ -124,7 +124,7 @@
String username) throws KustvaktException {
ParameterChecker.checkStringValue(clientId, "client_id");
OAuth2Client client = clientDao.retrieveClientById(clientId);
-
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<RefreshToken> query =
builder.createQuery(RefreshToken.class);
@@ -144,8 +144,7 @@
}
public List<RefreshToken> retrieveRefreshTokenByUser (String username,
- String clientId)
- throws KustvaktException {
+ String clientId) throws KustvaktException {
ParameterChecker.checkStringValue(username, "username");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
@@ -166,11 +165,35 @@
condition = builder.and(condition,
builder.equal(root.get(RefreshToken_.client), client));
}
-
+
query.select(root);
query.where(condition);
TypedQuery<RefreshToken> q = entityManager.createQuery(query);
return q.getResultList();
}
+
+ public void deleteInvalidRefreshTokens () {
+ List<RefreshToken> invalidRefreshTokens = retrieveInvalidRefreshTokens();
+ invalidRefreshTokens.forEach(token -> entityManager.remove(token));
+ }
+ public List<RefreshToken> retrieveInvalidRefreshTokens () {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<RefreshToken> query =
+ builder.createQuery(RefreshToken.class);
+
+ Root<RefreshToken> root = query.from(RefreshToken.class);
+ Predicate condition = builder.or(
+ builder.equal(root.get(RefreshToken_.isRevoked), true),
+ builder.lessThan(
+ 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/oltu/service/OltuTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
index 8741553..6bbed24 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
@@ -40,7 +40,6 @@
import de.ids_mannheim.korap.oauth2.oltu.OAuth2RevokeTokenSuperRequest;
import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
-import de.ids_mannheim.korap.utils.TimeUtils;
/** Implementation of token service using Apache Oltu.
*
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java
new file mode 100644
index 0000000..8000b64
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2AdminService.java
@@ -0,0 +1,24 @@
+package de.ids_mannheim.korap.oauth2.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+
+@Service
+public class OAuth2AdminService {
+
+ @Autowired
+ private AccessTokenDao tokenDao;
+ @Autowired
+ private RefreshTokenDao refreshDao;
+
+
+ public void cleanTokens () {
+ tokenDao.deleteInvalidAccessTokens();
+ refreshDao.deleteInvalidRefreshTokens();
+ }
+
+
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
new file mode 100644
index 0000000..336f1a8
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2AdminController.java
@@ -0,0 +1,56 @@
+package de.ids_mannheim.korap.web.controller;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+
+import com.sun.jersey.spi.container.ResourceFilters;
+
+import de.ids_mannheim.korap.constant.OAuth2Scope;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.service.OAuth2AdminService;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
+import de.ids_mannheim.korap.security.context.TokenContext;
+import de.ids_mannheim.korap.web.OAuth2ResponseHandler;
+import de.ids_mannheim.korap.web.filter.APIVersionFilter;
+import de.ids_mannheim.korap.web.filter.AdminFilter;
+import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
+import de.ids_mannheim.korap.web.filter.BlockingFilter;
+
+@Controller
+@Path("{version}/oauth2/admin")
+@ResourceFilters({ APIVersionFilter.class, AuthenticationFilter.class,
+ BlockingFilter.class, AdminFilter.class })
+public class OAuth2AdminController {
+
+ @Autowired
+ private OAuth2AdminService adminService;
+ @Autowired
+ private OAuth2ScopeService scopeService;
+ @Autowired
+ private OAuth2ResponseHandler responseHandler;
+
+ @POST
+ @Path("/token/clean")
+ public Response cleanExpiredInvalidToken (String token,
+ @Context SecurityContext securityContext) {
+
+ TokenContext context =
+ (TokenContext) securityContext.getUserPrincipal();
+
+ try {
+ scopeService.verifyScope(context, OAuth2Scope.ADMIN);
+ adminService.cleanTokens();
+
+ }
+ catch (KustvaktException e) {
+ throw responseHandler.throwit(e);
+ }
+ return Response.ok().build();
+ }
+}
diff --git a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
index fb70f79..858d238 100644
--- a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
+++ b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
@@ -50,13 +50,13 @@
INSERT INTO oauth2_access_token(token,user_id,created_date,
expiry_date, user_auth_time)
-VALUES("fia0123ikBWn931470H8s5gRqx7Moc4p","marlin","2018-05-30 16:25:50",
-"2018-05-31 16:25:50", "2018-05-30 16:23:10");
+VALUES("fia0123ikBWn931470H8s5gRqx7Moc4p","marlin",1527776750000,
+1527776750000, 1527690190000);
INSERT INTO oauth2_refresh_token(token,user_id,user_auth_time,
created_date, expiry_date, client)
-VALUES("js9iQ4lw1Ri7fz06l0dXl8fCVp3Yn7vmq8","pearl","2017-05-30 16:25:50",
-"2017-05-31 16:26:35", 1527784020000, "nW5qM63Rb2a7KdT9L");
+VALUES("js9iQ4lw1Ri7fz06l0dXl8fCVp3Yn7vmq8","pearl",1496154350000,
+1496240795000, 1527784020000, "nW5qM63Rb2a7KdT9L");
-- EM: expiry date must be in epoch milis format for testing with sqlite,
-- on the contrary, for testing using mysql use this format: "2018-05-31 16:27:00"
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
new file mode 100644
index 0000000..50ab44d
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AdminControllerTest.java
@@ -0,0 +1,45 @@
+package de.ids_mannheim.korap.web.controller;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.sun.jersey.api.client.ClientResponse;
+
+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.dao.AccessTokenDao;
+import de.ids_mannheim.korap.oauth2.dao.RefreshTokenDao;
+
+public class OAuth2AdminControllerTest extends OAuth2TestBase {
+
+ private String adminAuthHeader;
+ @Autowired
+ private RefreshTokenDao refreshDao;
+ @Autowired
+ private AccessTokenDao accessDao;
+
+ public OAuth2AdminControllerTest () throws KustvaktException {
+ adminAuthHeader = HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("admin", "password");
+ }
+
+ @Test
+ public void testCleanTokens () {
+ int refreshTokensBefore =
+ refreshDao.retrieveInvalidRefreshTokens().size();
+ assertTrue(refreshTokensBefore > 0);
+
+ int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
+ assertTrue(accessTokensBefore > 0);
+
+ resource().path(API_VERSION).path("oauth2").path("admin").path("token")
+ .path("clean").header(Attributes.AUTHORIZATION, adminAuthHeader)
+ .entity("token=adminToken").post(ClientResponse.class);
+ assertEquals(0, refreshDao.retrieveInvalidRefreshTokens().size());
+ assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
+ }
+}