From: Michael Arrastia Date: Fri, 8 Jun 2018 09:40:54 +0000 (+0100) Subject: Add validation of request headers X-Git-Tag: 1.3.0~25 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aai%2Fchamp.git;a=commitdiff_plain;h=fcf9451685f6de438a66b84189eba3a9b0db73a1 Add validation of request headers Enforces presence of X-FromAppId and X-TransactionId headers in REST requests. Change-Id: I539e863049e4d5a985d9e952ee7dcbf3fd97f7b3 Issue-ID: AAI-1194 Signed-off-by: Michael Arrastia --- diff --git a/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java b/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java index 726944b..b312af3 100644 --- a/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java +++ b/champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java @@ -69,6 +69,7 @@ import org.onap.champ.service.logging.ChampMsgs; import org.onap.champ.service.logging.LoggingUtil; import org.onap.champ.util.ChampProperties; import org.onap.champ.util.ChampServiceConstants; +import org.onap.champ.util.HttpHeadersValidator; import org.onap.champ.util.etag.EtagGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -81,6 +82,7 @@ public class ChampRESTAPI { private ChampDataService champDataService; private EtagGenerator etagGenerator; + private HttpHeadersValidator httpHeadersValidator; private String TRANSACTION_METHOD = "method"; private Timer timer; @@ -108,6 +110,7 @@ public class ChampRESTAPI { mapper.registerModule(module); etagGenerator = new EtagGenerator(); + httpHeadersValidator = new HttpHeadersValidator(); } @GET @@ -130,6 +133,7 @@ public class ChampRESTAPI { ChampObject retrieved; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { @@ -168,6 +172,7 @@ public class ChampRESTAPI { logger.info(ChampMsgs.INCOMING_REQUEST, tId, objectId); Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { @@ -201,6 +206,7 @@ public class ChampRESTAPI { logger.info(ChampMsgs.INCOMING_REQUEST, tId, champObj); Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { throw new ChampServiceException("transactionId not found", Status.BAD_REQUEST); @@ -240,6 +246,7 @@ public class ChampRESTAPI { Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { throw new ChampServiceException("transactionId not found", Status.BAD_REQUEST); @@ -277,6 +284,7 @@ public class ChampRESTAPI { Response response = null; ChampTransaction transaction = null; try { + httpHeadersValidator.validateRequestHeaders(headers); retrieved = champDataService.getRelationshipsByObject(oId, Optional.ofNullable(transaction)); EntityTag eTag = new EntityTag(etagGenerator.computeHashForChampRelationships(retrieved)); response = Response.status(Status.OK).entity(mapper.writeValueAsString(retrieved)).tag(eTag).build(); @@ -320,12 +328,12 @@ public class ChampRESTAPI { Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); champObjects = champDataService.queryObjects(filter, properties); EntityTag eTag = new EntityTag(etagGenerator.computeHashForChampObjects(champObjects)); response = Response.status(Status.OK).type(MediaType.APPLICATION_JSON).tag(eTag).entity(mapper.writeValueAsString(champObjects)) .build(); } catch (JsonProcessingException e) { - e.printStackTrace(); response = Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } catch (ChampServiceException e1) { response = Response.status(e1.getHttpStatus()).entity(e1.getMessage()).build(); @@ -350,6 +358,7 @@ public class ChampRESTAPI { ChampRelationship retrieved; Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { @@ -388,6 +397,7 @@ public class ChampRESTAPI { logger.info(ChampMsgs.INCOMING_REQUEST, tId, relationship); Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { throw new ChampServiceException("transactionId not found", Status.BAD_REQUEST); @@ -427,6 +437,7 @@ public class ChampRESTAPI { Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { throw new ChampServiceException("transactionId not found", Status.BAD_REQUEST); @@ -462,6 +473,7 @@ public class ChampRESTAPI { Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); ChampTransaction transaction = champDataService.getTransaction(tId); if (tId != null && transaction == null) { throw new ChampServiceException("transactionId not found", Status.BAD_REQUEST); @@ -499,12 +511,12 @@ public class ChampRESTAPI { } Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); champRelationshipList = champDataService.queryRelationships(filter); EntityTag eTag = new EntityTag(etagGenerator.computeHashForChampRelationships(champRelationshipList)); response = Response.status(Status.OK).type(MediaType.APPLICATION_JSON).tag(eTag).entity(mapper.writeValueAsString(champRelationshipList)) .build(); } catch (JsonProcessingException e) { - e.printStackTrace(); response = Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); } catch (ChampServiceException e1) { response = Response.status(e1.getHttpStatus()).entity(e1.getMessage()).build(); @@ -525,14 +537,19 @@ public class ChampRESTAPI { @Context HttpServletRequest req) { LoggingUtil.initMdcContext(req, headers); long startTimeInMs = System.currentTimeMillis(); - Status s; - String transaction = champDataService.openTransaction(); - - s = Status.OK; - Response response = Response.status(s).entity(transaction).build(); - logger.info(ChampMsgs.PROCESS_EVENT, "Opened Transaction with ID: " + transaction, s.toString()); - LoggingUtil.logRestRequest(logger, auditLogger, req, response); - metricsLogger.info(ChampMsgs.PROCESSED_REQUEST, "POST", Long.toString(System.currentTimeMillis() - startTimeInMs)); + Response response = null; + try { + httpHeadersValidator.validateRequestHeaders(headers); + String transaction = champDataService.openTransaction(); + Status s = Status.OK; + response = Response.status(s).entity(transaction).build(); + logger.info(ChampMsgs.PROCESS_EVENT, "Opened Transaction with ID: " + transaction, s.toString()); + } catch (ChampServiceException e) { + response = Response.status(e.getHttpStatus()).entity(e.getMessage()).build(); + } finally { + LoggingUtil.logRestRequest(logger, auditLogger, req, response); + metricsLogger.info(ChampMsgs.PROCESSED_REQUEST, "POST", Long.toString(System.currentTimeMillis() - startTimeInMs)); + } return response; } @@ -551,9 +568,12 @@ public class ChampRESTAPI { } try { + httpHeadersValidator.validateRequestHeaders(headers); response = Response.status(Status.OK).entity(mapper.writeValueAsString(tId + " is OPEN")).build(); } catch (JsonProcessingException e) { response = Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build(); + } catch (ChampServiceException e) { + response = Response.status(e.getHttpStatus()).entity(e.getMessage()).build(); } catch (Exception e) { response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build(); LoggingUtil.logInternalError(logger, e); @@ -576,6 +596,7 @@ public class ChampRESTAPI { Response response = null; try { + httpHeadersValidator.validateRequestHeaders(headers); JSONObject jsonObj = new JSONObject(t); String method = jsonObj.getString(this.TRANSACTION_METHOD); @@ -606,6 +627,7 @@ public class ChampRESTAPI { } return response; } + private boolean reservedKeyMatcher(Pattern p, String key) { Matcher m = p.matcher ( key ); if (m.matches()) { diff --git a/champ-service/src/main/java/org/onap/champ/util/HttpHeadersValidator.java b/champ-service/src/main/java/org/onap/champ/util/HttpHeadersValidator.java new file mode 100644 index 0000000..df85997 --- /dev/null +++ b/champ-service/src/main/java/org/onap/champ/util/HttpHeadersValidator.java @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START========================================== + * org.onap.aai + * =================================================================== + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2018 Amdocs + * =================================================================== + * 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. + * ============LICENSE_END============================================ + */ +package org.onap.champ.util; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response.Status; +import org.onap.champ.exception.ChampServiceException; + +public class HttpHeadersValidator { + + public void validateRequestHeaders(HttpHeaders headers) throws ChampServiceException { + String sourceOfTruth = null; + if (headers.getRequestHeaders().containsKey("X-FromAppId")) { + sourceOfTruth = headers.getRequestHeaders().getFirst("X-FromAppId"); + } + + if (sourceOfTruth == null || sourceOfTruth.trim() == "") { + throw new ChampServiceException("Invalid request, Missing X-FromAppId header", Status.BAD_REQUEST); + } + + String transId = null; + if (headers.getRequestHeaders().containsKey("X-TransactionId")) { + transId = headers.getRequestHeaders().getFirst("X-TransactionId"); + } + + if (transId == null || transId.trim() == "") { + throw new ChampServiceException("Invalid request, Missing X-TransactionId header", Status.BAD_REQUEST); + } + } +} diff --git a/champ-service/src/test/java/org/onap/champ/util/MockHeaders.java b/champ-service/src/test/java/org/onap/champ/util/MockHeaders.java new file mode 100644 index 0000000..1b248fb --- /dev/null +++ b/champ-service/src/test/java/org/onap/champ/util/MockHeaders.java @@ -0,0 +1,100 @@ +/** + * ============LICENSE_START========================================== + * org.onap.aai + * =================================================================== + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2018 Amdocs + * =================================================================== + * 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. + * ============LICENSE_END============================================ + */ +package org.onap.champ.util; + +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +public class MockHeaders implements HttpHeaders { + + private MultivaluedMap headers; + + public MockHeaders() { + headers = new MultivaluedHashMap(); + headers.add("X-FromAppId", "test-app"); + headers.add("X-TransactionId", "65f7e29c-57fd-45b2-bfd5-19e25c59110e"); + } + + @Override + public List getAcceptableLanguages() { + return null; + } + + @Override + public List getAcceptableMediaTypes() { + return null; + } + + @Override + public Map getCookies() { + return null; + } + + @Override + public Date getDate() { + return null; + } + + @Override + public String getHeaderString(String arg0) { + return null; + } + + @Override + public Locale getLanguage() { + return null; + } + + @Override + public int getLength() { + return 0; + } + + @Override + public MediaType getMediaType() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequestHeader(String arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public MultivaluedMap getRequestHeaders() { + return headers; + } + + public void clearRequestHeader(String... keys) { + for (String key : keys) { + headers.remove(key); + } + } +} diff --git a/champ-service/src/test/java/org/onap/champ/util/TestHttpHeadersValidator.java b/champ-service/src/test/java/org/onap/champ/util/TestHttpHeadersValidator.java new file mode 100644 index 0000000..aa00dc1 --- /dev/null +++ b/champ-service/src/test/java/org/onap/champ/util/TestHttpHeadersValidator.java @@ -0,0 +1,75 @@ +/** + * ============LICENSE_START========================================== + * org.onap.aai + * =================================================================== + * Copyright © 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017-2018 Amdocs + * =================================================================== + * 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. + * ============LICENSE_END============================================ + */ +package org.onap.champ.util; + +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.onap.champ.exception.ChampServiceException; + +public class TestHttpHeadersValidator { + + private static HttpHeadersValidator champRestApi; + + @BeforeClass + public static void setupBeforeClass() throws Exception { + champRestApi = new HttpHeadersValidator(); + } + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testValidRequestHeader() throws ChampServiceException { + champRestApi.validateRequestHeaders(new MockHeaders()); + } + + @Test + public void testInvalidRequestHeaderXTransactionId() throws ChampServiceException { + thrown.expect(ChampServiceException.class); + thrown.expectMessage("Invalid request, Missing X-TransactionId header"); + + MockHeaders MockHeaders = new MockHeaders(); + MockHeaders.clearRequestHeader("X-TransactionId"); + champRestApi.validateRequestHeaders(MockHeaders); + } + + @Test + public void testInvalidRequestHeaderXFromAppId() throws ChampServiceException { + thrown.expect(ChampServiceException.class); + thrown.expectMessage("Invalid request, Missing X-FromAppId header"); + + MockHeaders MockHeaders = new MockHeaders(); + MockHeaders.clearRequestHeader("X-FromAppId"); + champRestApi.validateRequestHeaders(MockHeaders); + } + + @Test + public void testEmptyRequestHeader() throws ChampServiceException { + thrown.expect(ChampServiceException.class); + thrown.expectMessage("Invalid request, Missing X-FromAppId header"); + + MockHeaders MockHeaders = new MockHeaders(); + MockHeaders.clearRequestHeader("X-TransactionId", "X-FromAppId"); + champRestApi.validateRequestHeaders(MockHeaders); + } +}