Add validation of request headers 33/51033/1
authorMichael Arrastia <MArrasti@amdocs.com>
Fri, 8 Jun 2018 09:40:54 +0000 (10:40 +0100)
committerMichael Arrastia <MArrasti@amdocs.com>
Fri, 8 Jun 2018 09:40:54 +0000 (10:40 +0100)
Enforces presence of X-FromAppId and X-TransactionId headers
in REST requests.

Change-Id: I539e863049e4d5a985d9e952ee7dcbf3fd97f7b3
Issue-ID: AAI-1194
Signed-off-by: Michael Arrastia <MArrasti@amdocs.com>
champ-service/src/main/java/org/onap/champ/ChampRESTAPI.java
champ-service/src/main/java/org/onap/champ/util/HttpHeadersValidator.java [new file with mode: 0644]
champ-service/src/test/java/org/onap/champ/util/MockHeaders.java [new file with mode: 0644]
champ-service/src/test/java/org/onap/champ/util/TestHttpHeadersValidator.java [new file with mode: 0644]

index 726944b..b312af3 100644 (file)
@@ -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 (file)
index 0000000..df85997
--- /dev/null
@@ -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 (file)
index 0000000..1b248fb
--- /dev/null
@@ -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<String, String> headers;
+
+    public MockHeaders() {
+        headers = new MultivaluedHashMap<String, String>();
+        headers.add("X-FromAppId", "test-app");
+        headers.add("X-TransactionId", "65f7e29c-57fd-45b2-bfd5-19e25c59110e");
+    }
+
+    @Override
+    public List<Locale> getAcceptableLanguages() {
+        return null;
+    }
+
+    @Override
+    public List<MediaType> getAcceptableMediaTypes() {
+        return null;
+    }
+
+    @Override
+    public Map<String, Cookie> 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<String> getRequestHeader(String arg0) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public MultivaluedMap<String, String> 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 (file)
index 0000000..aa00dc1
--- /dev/null
@@ -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);
+    }
+}