From 0846b7b1801eff9d874cb25b8f9a49e76b0b3849 Mon Sep 17 00:00:00 2001 From: Stefan Negrea Date: Tue, 27 Sep 2016 15:36:11 -0500 Subject: [PATCH] [HWKMETRICS-478] Make a separate admin filter that does not get packaged in the container distribution. --- .../metrics/api/jaxrs/filter/AdminFilter.java | 112 ++++++++++++++++++ .../api/jaxrs/filter/TenantFilter.java | 48 +------- .../metrics-api-jaxrs-openshift/pom.xml | 3 + .../hawkular/metrics/rest/TenantITest.groovy | 85 ++++++++++--- 4 files changed, 186 insertions(+), 62 deletions(-) create mode 100644 api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/AdminFilter.java diff --git a/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/AdminFilter.java b/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/AdminFilter.java new file mode 100644 index 000000000..09b45d1cf --- /dev/null +++ b/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/AdminFilter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014-2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.hawkular.metrics.api.jaxrs.filter; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +import java.io.IOException; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.Provider; + +import org.hawkular.metrics.model.ApiError; +import org.hawkular.metrics.sysconfig.ConfigurationService; + +import com.google.common.base.Charsets; +import com.google.common.hash.Hashing; + +/** + * @author Stefan Negrea + */ +@Provider +@PreMatching +@Priority(20) +public class AdminFilter implements ContainerRequestFilter { + public static final String TENANT_HEADER_NAME = "Hawkular-Tenant"; + public static final String ADMIN_TOKEN_HEADER_NAME = "Hawkular-Admin-Token"; + + private static final String MISSING_TENANT_MSG; + private static final String WRONG_ADMIN_TOKEN_MSG; + + static { + MISSING_TENANT_MSG = "Tenant is not specified. Use '" + + TENANT_HEADER_NAME + + "' header."; + + WRONG_ADMIN_TOKEN_MSG = "Admin token is wrong or not specified."; + } + + @Inject + private ConfigurationService configurationService; + + private String savedAdminToken; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + UriInfo uriInfo = requestContext.getUriInfo(); + String path = uriInfo.getPath(); + + if (path.startsWith("/tenants")) { + String tenant = requestContext.getHeaders().getFirst(TENANT_HEADER_NAME); + if (tenant == null || tenant.trim().isEmpty()) { + // Fail on missing tenant info + Response response = Response.status(Status.BAD_REQUEST) + .type(APPLICATION_JSON_TYPE) + .entity(new ApiError(MISSING_TENANT_MSG)) + .build(); + requestContext.abortWith(response); + return; + } + + String adminToken = requestContext.getHeaders().getFirst(ADMIN_TOKEN_HEADER_NAME); + if (adminToken != null && !adminToken.trim().isEmpty() && validAdminToken(adminToken)) { + return; + } else { + // Fail on bad admin token + Response response = Response.status(Status.BAD_REQUEST) + .type(APPLICATION_JSON_TYPE) + .entity(new ApiError(WRONG_ADMIN_TOKEN_MSG)) + .build(); + requestContext.abortWith(response); + return; + } + } + } + + private boolean validAdminToken(String adminToken) { + if (savedAdminToken == null) { + savedAdminToken = configurationService.load("org.hawkular.metrics", "admin.token").toBlocking() + .firstOrDefault(""); + } + + adminToken = Hashing.sha256().newHasher().putString(adminToken, Charsets.UTF_8).hash().toString(); + + if (adminToken.equals(savedAdminToken)) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/TenantFilter.java b/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/TenantFilter.java index e9feae083..9e546d52a 100644 --- a/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/TenantFilter.java +++ b/api/metrics-api-jaxrs/src/main/java/org/hawkular/metrics/api/jaxrs/filter/TenantFilter.java @@ -21,7 +21,7 @@ import java.io.IOException; -import javax.inject.Inject; +import javax.annotation.Priority; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; @@ -33,57 +33,30 @@ import org.hawkular.metrics.api.jaxrs.handler.BaseHandler; import org.hawkular.metrics.api.jaxrs.handler.StatusHandler; import org.hawkular.metrics.model.ApiError; -import org.hawkular.metrics.sysconfig.ConfigurationService; - -import com.google.common.base.Charsets; -import com.google.common.hash.Hashing; /** * @author Stefan Negrea */ @Provider @PreMatching +@Priority(10) public class TenantFilter implements ContainerRequestFilter { public static final String TENANT_HEADER_NAME = "Hawkular-Tenant"; - public static final String ADMIN_TOKEN_HEADER_NAME = "Hawkular-Admin-Token"; private static final String MISSING_TENANT_MSG; - private static final String WRONG_ADMIN_TOKEN_MSG; static { MISSING_TENANT_MSG = "Tenant is not specified. Use '" + TENANT_HEADER_NAME + "' header."; - - WRONG_ADMIN_TOKEN_MSG = "Admin token is wrong or not specified."; } - @Inject - private ConfigurationService configurationService; - - private String savedAdminToken; - @Override public void filter(ContainerRequestContext requestContext) throws IOException { UriInfo uriInfo = requestContext.getUriInfo(); String path = uriInfo.getPath(); - if (path.startsWith("/tenants")) { - String adminToken = requestContext.getHeaders().getFirst(ADMIN_TOKEN_HEADER_NAME); - if (adminToken != null && !adminToken.trim().isEmpty() && validAdminToken(adminToken)) { - return; - } else { - // Fail on bad admin token - Response response = Response.status(Status.BAD_REQUEST) - .type(APPLICATION_JSON_TYPE) - .entity(new ApiError(WRONG_ADMIN_TOKEN_MSG)) - .build(); - requestContext.abortWith(response); - return; - } - } - - if (path.startsWith(StatusHandler.PATH) || path.equals(BaseHandler.PATH)) { + if (path.startsWith("/tenants") || path.startsWith(StatusHandler.PATH) || path.equals(BaseHandler.PATH)) { // Some handlers do not check the tenant header return; } @@ -100,19 +73,4 @@ public void filter(ContainerRequestContext requestContext) throws IOException { .build(); requestContext.abortWith(response); } - - private boolean validAdminToken(String adminToken) { - if (savedAdminToken == null) { - savedAdminToken = configurationService.load("org.hawkular.metrics", "admin.token").toBlocking() - .firstOrDefault(""); - } - - adminToken = Hashing.sha256().newHasher().putString(adminToken, Charsets.UTF_8).hash().toString(); - - if (adminToken.equals(savedAdminToken)) { - return true; - } - - return false; - } } \ No newline at end of file diff --git a/containers/metrics-api-jaxrs-openshift/pom.xml b/containers/metrics-api-jaxrs-openshift/pom.xml index 998deae2f..6e09b84fd 100644 --- a/containers/metrics-api-jaxrs-openshift/pom.xml +++ b/containers/metrics-api-jaxrs-openshift/pom.xml @@ -67,6 +67,9 @@ org.hawkular.metrics hawkular-metrics-api-jaxrs + + WEB-INF/classes/org/hawkular/metrics/api/jaxrs/filter/AdminFilter.class + diff --git a/integration-tests/rest-tests-jaxrs/src/test/groovy/org/hawkular/metrics/rest/TenantITest.groovy b/integration-tests/rest-tests-jaxrs/src/test/groovy/org/hawkular/metrics/rest/TenantITest.groovy index 601aaa2f8..f507d21d2 100644 --- a/integration-tests/rest-tests-jaxrs/src/test/groovy/org/hawkular/metrics/rest/TenantITest.groovy +++ b/integration-tests/rest-tests-jaxrs/src/test/groovy/org/hawkular/metrics/rest/TenantITest.groovy @@ -40,7 +40,10 @@ class TenantITest extends RESTTest { def secondTenantId = nextTenantId() def response = hawkularMetrics.post(path: "tenants", - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ id : tenantId, retentions: [gauge: 45, availability: 30, counter: 13] @@ -49,7 +52,10 @@ class TenantITest extends RESTTest { assertEquals("http://$baseURI/tenants".toString(), response.getFirstHeader('location').value) response = hawkularMetrics.post(path: "tenants", - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ id : secondTenantId, retentions: [gauge: 13, availability: 45, counter: 30] @@ -58,7 +64,10 @@ class TenantITest extends RESTTest { assertEquals("http://$baseURI/tenants".toString(), response.getFirstHeader('location').value) response = hawkularMetrics.get(path: "tenants", - headers: [(adminTokenHeaderName): adminToken] + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ] ) def expectedData = [ [ @@ -77,19 +86,28 @@ class TenantITest extends RESTTest { @Test void duplicateTenantTest() { def response = hawkularMetrics.post(path: 'tenants', - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [id: tenantId]) assertEquals(201, response.status) assertEquals("http://$baseURI/tenants".toString(), response.getFirstHeader('location').value) badPost(path: 'tenants', - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [id: tenantId]) { exception -> assertEquals(409, exception.response.status) } response = hawkularMetrics.post(path: 'tenants', - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], query: [overwrite: true], body: [ id: tenantId, @@ -98,7 +116,11 @@ class TenantITest extends RESTTest { assertEquals(201, response.status) assertEquals("http://$baseURI/tenants".toString(), response.getFirstHeader('location').value) - response = hawkularMetrics.get(path: "tenants", headers: [(adminTokenHeaderName): adminToken],) + response = hawkularMetrics.get(path: "tenants", + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ]) def expectedData = [ [ id : tenantId, @@ -111,7 +133,12 @@ class TenantITest extends RESTTest { @Test void invalidPayloadTest() { - badPost(path: 'tenants', headers: [(adminTokenHeaderName): adminToken], body: "" /* Empty body */) { exception -> + badPost(path: 'tenants', + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], + body: "" /* Empty body */) { exception -> assertEquals(400, exception.response.status) assertTrue(exception.response.data.containsKey("errorMsg")) } @@ -125,7 +152,10 @@ class TenantITest extends RESTTest { def response = hawkularMetrics.post( path: "gauges/G1/raw", - headers: [(tenantHeaderName): tenantId], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ [timestamp: start.minusMinutes(1).millis, value: 3.14] ] @@ -149,7 +179,10 @@ class TenantITest extends RESTTest { def response = hawkularMetrics.post( path: 'counters/C1/raw', - headers: [(tenantHeaderName): tenantId], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ [timestamp: start.minusMinutes(1).millis, value: 100] ] @@ -159,7 +192,12 @@ class TenantITest extends RESTTest { response = hawkularMetrics.get(path: 'clock/wait', query: [duration: '31mn']) assertEquals("There was an error waiting: $response.data", 200, response.status) - response = hawkularMetrics.get(path: 'tenants') + response = hawkularMetrics.get(path: 'tenants', + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ]) + assertEquals(200, response.status) assertNotNull("tenantId = $tenantId, Response = $response.data", response.data.find { it.id == tenantId }) @@ -173,7 +211,10 @@ class TenantITest extends RESTTest { def response = hawkularMetrics.post( path: 'availability/A1/raw', - headers: [(tenantHeaderName): tenantId], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ [timestamp: start.minusMinutes(1).millis, value: 'up'] ] @@ -183,7 +224,11 @@ class TenantITest extends RESTTest { response = hawkularMetrics.get(path: 'clock/wait', query: [duration: '31mn']) assertEquals("There was an error waiting: $response.data", 200, response.status) - response = hawkularMetrics.get(path: 'tenants') + response = hawkularMetrics.get(path: 'tenants', + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ]) assertEquals(200, response.status) assertNotNull("tenantId = $tenantId, Response = $response.data", response.data.find { it.id == tenantId }) @@ -192,11 +237,14 @@ class TenantITest extends RESTTest { @Test void deleteTenantHavingNoMetrics() { def response = hawkularMetrics.post(path: "tenants", - headers: [(adminTokenHeaderName): adminToken], + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ], body: [ - id : tenantId, + id: tenantId, retentions: [gauge: 45, availability: 30, counter: 13] - ]) + ]) assertEquals(201, response.status) response = hawkularMetrics.get(path:'scheduler/clock', headers: [(tenantHeaderName): tenantId]) @@ -216,7 +264,10 @@ class TenantITest extends RESTTest { assertEquals(200, response.status) response = hawkularMetrics.get(path: 'tenants', - headers: [(tenantHeaderName): tenantId, (adminTokenHeaderName): adminToken]) + headers: [ + (tenantHeaderName): tenantId, + (adminTokenHeaderName): adminToken + ]) assertEquals(200, response.status) assertNull(response.data.find { it.id == tenantId }) }