Allow admin access using admin token for the clean token API
Change-Id: Id3c6db09b59cd7384f508a5b1d19c0b3084892b9
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
index 1201529..885ce41 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
@@ -18,6 +18,9 @@
import de.ids_mannheim.korap.web.KustvaktResponseHandler;
/**
+ * Verifies admin credentials or token before allowing access to
+ * administrative services
+ *
* @author hanl, margaretha
*
* @see {@link AuthenticationFilter}
@@ -29,6 +32,7 @@
private @Context ServletContext servletContext;
@Autowired
private AdminDao adminDao;
+
@Autowired
private KustvaktResponseHandler kustvaktResponseHandler;
@@ -36,28 +40,37 @@
public void filter (ContainerRequestContext context) {
super.filter(context);
String username = "guest";
-
- // legacy support for kustvakt core
- String adminToken = JerseyUtils.getFormParameters(context).asMap().getFirst("token");
+ String adminToken = JerseyUtils.getFormParameters(context).asMap()
+ .getFirst("token");
+ if (!checkAdminToken(adminToken)) {
+ SecurityContext securityContext = context.getSecurityContext();
+ TokenContext tokenContext = (TokenContext) securityContext
+ .getUserPrincipal();
+ checkAdminCredentials(tokenContext, username);
+ }
+ }
+
+
+ private boolean checkAdminToken (String adminToken) {
if (adminToken != null && !adminToken.isEmpty()) {
- // startswith token=
- // adminToken = adminToken.substring(6);
if (adminToken
.equals(servletContext.getInitParameter("adminToken"))) {
- return;
+ return true;
}
}
+ return false;
+ }
- SecurityContext securityContext = context.getSecurityContext();
- TokenContext tokenContext = (TokenContext) securityContext
- .getUserPrincipal();
-
+
+ private void checkAdminCredentials (TokenContext tokenContext,
+ String username) {
if (tokenContext != null) {
username = tokenContext.getUsername();
if (adminDao.isAdmin(username)) {
return;
}
}
+
throw kustvaktResponseHandler.throwit(new KustvaktException(
StatusCodes.AUTHORIZATION_FAILED,
"Unauthorized operation for user: " + username, username));
diff --git a/full/Changes b/full/Changes
index 0ceb78d..e4c7a6c 100644
--- a/full/Changes
+++ b/full/Changes
@@ -11,6 +11,8 @@
- Allowed OAuth2 clients to use localhost as redirect URIs.
2023-02-03
- Fixed content-type in error responses by changing it to application/json
+2023-02-06
+- Allow admin access using admin token for the clean token API
# version 0.69.1
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
index 9027071..0ca1368 100644
--- 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
@@ -2,28 +2,21 @@
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
-import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
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 de.ids_mannheim.korap.web.utils.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.utils.ResourceFilters;
@Controller
@Path("{version}/oauth2/admin")
@@ -34,26 +27,18 @@
@Autowired
private OAuth2AdminService adminService;
@Autowired
- private OAuth2ScopeService scopeService;
- @Autowired
private OAuth2ResponseHandler responseHandler;
- @GET
+ /**
+ * Removes expired or invalid access and refresh tokens from
+ * database and cache
+ *
+ * @return Response status OK, if successful
+ */
+ @POST
@Path("token/clean")
- public Response cleanExpiredInvalidToken (
- @Context SecurityContext securityContext) {
-
- TokenContext context =
- (TokenContext) securityContext.getUserPrincipal();
-
- try {
- scopeService.verifyScope(context, OAuth2Scope.ADMIN);
- adminService.cleanTokens();
-
- }
- catch (KustvaktException e) {
- throw responseHandler.throwit(e);
- }
+ public Response cleanExpiredInvalidToken () {
+ adminService.cleanTokens();
return Response.ok().build();
}
@@ -68,7 +53,6 @@
* When degrading super clients, all existing tokens and
* authorization codes are invalidated.
*
- * @param securityContext
* @param clientId
* OAuth2 client id
* @param super
@@ -79,13 +63,9 @@
@Path("client/privilege")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response updateClientPrivilege (
- @Context SecurityContext securityContext,
@FormParam("client_id") String clientId,
@FormParam("super") String isSuper) {
- TokenContext context =
- (TokenContext) securityContext.getUserPrincipal();
try {
- scopeService.verifyScope(context, OAuth2Scope.ADMIN);
adminService.updatePrivilege(clientId, Boolean.valueOf(isSuper));
return Response.ok("SUCCESS").build();
}
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
index db29dec..42431c8 100644
--- 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
@@ -3,8 +3,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
-import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import org.apache.http.entity.ContentType;
import org.junit.Test;
@@ -12,10 +15,6 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
-import javax.ws.rs.ProcessingException;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.core.Response.Status;
import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
@@ -59,25 +58,37 @@
return response;
}
- private void updateClientPriviledge (String clientId, boolean isSuper)
+ private Response updateClientPrivilegeWithAdminToken (String clientId)
throws ProcessingException,
KustvaktException {
+
Form form = new Form();
form.param("client_id", clientId);
- form.param("super", Boolean.toString(isSuper));
+ form.param("super", Boolean.toString(false));
+ form.param("token", "secret"); //adminToken
+
+ Response response = target().path(API_VERSION).path("oauth2")
+ .path("admin").path("client").path("privilege")
+ .request()
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .post(Entity.form(form));
+ return response;
+ }
+
+
+ private void testUpdateClientPriviledgeUnauthorized (Form form)
+ throws ProcessingException, KustvaktException {
Response response = updateClientPrivilege(username, form);
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
assertEquals(StatusCodes.AUTHORIZATION_FAILED,
node.at("/errors/0/0").asInt());
-
- response = updateClientPrivilege("admin", form);
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
}
@Test
- public void testCleanExpiredTokens () {
+ public void testCleanExpiredTokensUsingAdminToken () {
int refreshTokensBefore =
refreshDao.retrieveInvalidRefreshTokens().size();
assertTrue(refreshTokensBefore > 0);
@@ -85,11 +96,13 @@
int accessTokensBefore = accessDao.retrieveInvalidAccessTokens().size();
assertTrue(accessTokensBefore > 0);
+ Form form = new Form();
+ form.param("token", "secret");
+
target().path(API_VERSION).path("oauth2").path("admin").path("token")
.path("clean")
.request()
- .header(Attributes.AUTHORIZATION, adminAuthHeader)
- .get();
+ .post(Entity.form(form));
assertEquals(0, refreshDao.retrieveInvalidRefreshTokens().size());
assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
@@ -116,7 +129,7 @@
.path("clean")
.request()
.header(Attributes.AUTHORIZATION, adminAuthHeader)
- .get();
+ .post(null);
assertEquals(0, accessDao.retrieveInvalidAccessTokens().size());
}
@@ -137,10 +150,22 @@
clientAuthHeader);
String accessToken = node.at("/access_token").asText();
- updateClientPriviledge(clientId, true);
+ //update client priviledge to super client
+ Form form = new Form();
+ form.param("client_id", clientId);
+ form.param("super", Boolean.toString(true));
+
+ testUpdateClientPriviledgeUnauthorized(form);
+
+ response = updateClientPrivilege("admin", form);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
testAccessTokenAfterUpgradingClient(clientId, accessToken);
- updateClientPriviledge(clientId, false);
+ // degrade a super client to a common client
+
+ updateClientPrivilegeWithAdminToken(clientId);
+
testAccessTokenAfterDegradingSuperClient(clientId, accessToken);
deregisterClient(username, clientId);