XSS Vulnerability fix in SharedContextRestController
[portal.git] / ecomp-portal-BE-common / src / main / java / org / onap / portalapp / portal / controller / SharedContextRestController.java
1 /*-
2  * ============LICENSE_START==========================================
3  * ONAP Portal
4  * ===================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ===================================================================
7  *
8  * Unless otherwise specified, all software contained herein is licensed
9  * under the Apache License, Version 2.0 (the "License");
10  * you may not use this software except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *             http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  * Unless otherwise specified, all documentation contained herein is licensed
22  * under the Creative Commons License, Attribution 4.0 Intl. (the "License");
23  * you may not use this documentation except in compliance with the License.
24  * You may obtain a copy of the License at
25  *
26  *             https://creativecommons.org/licenses/by/4.0/
27  *
28  * Unless required by applicable law or agreed to in writing, documentation
29  * distributed under the License is distributed on an "AS IS" BASIS,
30  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31  * See the License for the specific language governing permissions and
32  * limitations under the License.
33  *
34  * ============LICENSE_END============================================
35  *
36  * 
37  */
38 package org.onap.portalapp.portal.controller;
39
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45
46 import javax.servlet.http.HttpServletRequest;
47 import javax.servlet.http.HttpServletResponse;
48
49 import org.onap.portalapp.controller.EPRestrictedRESTfulBaseController;
50 import org.onap.portalapp.portal.domain.SharedContext;
51 import org.onap.portalapp.portal.exceptions.NotValidDataException;
52 import org.onap.portalapp.portal.logging.aop.EPAuditLog;
53 import org.onap.portalapp.portal.service.SharedContextService;
54 import org.onap.portalapp.portal.utils.EPCommonSystemProperties;
55 import org.onap.portalapp.portal.utils.PortalConstants;
56 import org.onap.portalapp.validation.DataValidator;
57 import org.onap.portalapp.validation.SecureString;
58 import org.onap.portalsdk.core.logging.logic.EELFLoggerDelegate;
59 import org.springframework.beans.factory.annotation.Autowired;
60 import org.springframework.context.annotation.Configuration;
61 import org.springframework.context.annotation.EnableAspectJAutoProxy;
62 import org.springframework.http.HttpStatus;
63 import org.springframework.web.bind.annotation.ExceptionHandler;
64 import org.springframework.web.bind.annotation.RequestBody;
65 import org.springframework.web.bind.annotation.RequestMapping;
66 import org.springframework.web.bind.annotation.RequestMethod;
67 import org.springframework.web.bind.annotation.RequestParam;
68 import org.springframework.web.bind.annotation.RestController;
69
70 import com.fasterxml.jackson.core.JsonProcessingException;
71 import com.fasterxml.jackson.databind.ObjectMapper;
72
73 import io.swagger.annotations.ApiOperation;
74
75 /**
76  * The shared-context feature allows onboarded applications to share data among
77  * themselves easily for a given session. It basically implements a Java map:
78  * put or get a key-value pair within a map identified by a session ID.
79  * 
80  * This REST endpoint listens on the Portal app server and answers requests made
81  * by back-end application servers. Reads and writes values to the database
82  * using a Hibernate service to ensure all servers in a high-availability
83  * cluster see the same data.
84  */
85 @Configuration
86 @RestController
87 @RequestMapping(PortalConstants.REST_AUX_API + "/context")
88 @EnableAspectJAutoProxy
89 @EPAuditLog
90 public class SharedContextRestController extends EPRestrictedRESTfulBaseController {
91         private static final DataValidator dataValidator = new DataValidator();
92         private static final EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(SharedContextRestController.class);
93         private static final ObjectMapper mapper = new ObjectMapper();
94
95         private SharedContextService contextService;
96
97         @Autowired
98         public SharedContextRestController(SharedContextService contextService) {
99                 this.contextService = contextService;
100         }
101
102         /**
103          * Gets a value for the specified context and key (RESTful service method).
104          *
105          * @param request
106          *            HTTP servlet request
107          * @param context_id
108          *            ID that identifies the context, usually the ONAP Portal
109          *            session key.
110          * @param ckey
111          *            Key for the key-value pair to fetch
112          * @return JSON with shared context object; response=null if not found.
113          * @throws Exception
114          *             on bad arguments
115          */
116         @ApiOperation(value = "Gets a value for the specified context and key.", response = SharedContext.class)
117         @RequestMapping(value = { "/get" }, method = RequestMethod.GET, produces = "application/json")
118         public String getContext(HttpServletRequest request, @RequestParam String context_id, @RequestParam String ckey)
119                         throws Exception {
120                 logger.debug(EELFLoggerDelegate.debugLogger, "getContext for ID " + context_id + ", key " + ckey);
121                 if (context_id == null || ckey == null)
122                         throw new Exception("Received null for context_id and/or ckey");
123                 SecureString secureContextId = new SecureString(context_id);
124                 SecureString secureCKey = new SecureString(ckey);
125
126                 if(!dataValidator.isValid(secureContextId) || !dataValidator.isValid(secureCKey)){
127                         throw new NotValidDataException("Received not valid for context_id and/or ckey");
128                 }
129
130                 SharedContext context = contextService.getSharedContext(context_id, ckey);
131                 String jsonResponse;
132                 if (context == null)
133                         jsonResponse = convertResponseToJSON(context);
134                 else
135                         jsonResponse = mapper.writeValueAsString(context);
136
137                 return jsonResponse;
138         }
139
140         /**
141          * Gets user information for the specified context (RESTful service method).
142          *
143          * @param request
144          *            HTTP servlet request
145          * @param context_id
146          *            ID that identifies the context, usually the ONAP Portal
147          *            session key.
148          * @return List of shared-context objects as JSON; should have user's first
149          *         name, last name and email address; null if none found
150          * @throws Exception
151          *             on bad arguments
152          */
153         @ApiOperation(value = "Gets user information for the specified context.", response = SharedContext.class, responseContainer = "List")
154         @RequestMapping(value = { "/get_user" }, method = RequestMethod.GET, produces = "application/json")
155         public String getUserContext(HttpServletRequest request, @RequestParam String context_id) throws Exception {
156
157                 logger.debug(EELFLoggerDelegate.debugLogger, "getUserContext for ID " + context_id);
158                 if (context_id == null)
159                         throw new Exception("Received null for context_id");
160                 SecureString secureContextId = new SecureString(context_id);
161                 if (!dataValidator.isValid(secureContextId))
162                         throw new NotValidDataException("context_id is not valid");
163
164                 List<SharedContext> listSharedContext = new ArrayList<>();
165                 SharedContext firstNameContext = contextService.getSharedContext(context_id,
166                                 EPCommonSystemProperties.USER_FIRST_NAME);
167                 SharedContext lastNameContext = contextService.getSharedContext(context_id,
168                                 EPCommonSystemProperties.USER_LAST_NAME);
169                 SharedContext emailContext = contextService.getSharedContext(context_id, EPCommonSystemProperties.USER_EMAIL);
170                 SharedContext orgUserIdContext = contextService.getSharedContext(context_id,
171                                 EPCommonSystemProperties.USER_ORG_USERID);
172                 if (firstNameContext != null)
173                         listSharedContext.add(firstNameContext);
174                 if (lastNameContext != null)
175                         listSharedContext.add(lastNameContext);
176                 if (emailContext != null)
177                         listSharedContext.add(emailContext);
178                 if (orgUserIdContext != null)
179                         listSharedContext.add(orgUserIdContext);
180                 return convertResponseToJSON(listSharedContext);
181         }
182
183         /**
184          * Tests for presence of the specified key in the specified context (RESTful
185          * service method).
186          *
187          * @param request
188          *            HTTP servlet request
189          * @param context_id
190          *            ID that identifies the context, usually the ONAP Portal
191          *            session key.
192          * @param ckey
193          *            Key for the key-value pair to test
194          * @return JSON with result indicating whether the context and key were
195          *         found.
196          * @throws Exception
197          *             on bad arguments
198          */
199         @ApiOperation(value = "Tests for presence of the specified key in the specified context.", response = SharedContextJsonResponse.class)
200         @RequestMapping(value = { "/check" }, method = RequestMethod.GET, produces = "application/json")
201         public String checkContext(HttpServletRequest request, @RequestParam String context_id, @RequestParam String ckey)
202                         throws Exception {
203
204                 logger.debug(EELFLoggerDelegate.debugLogger, "checkContext for " + context_id + ", key " + ckey);
205                 if (context_id == null || ckey == null)
206                         throw new Exception("Received null for contextId and/or key");
207
208                 SecureString secureContextId = new SecureString(context_id);
209                 SecureString secureCKey = new SecureString(ckey);
210
211                 if (!dataValidator.isValid(secureContextId) || !dataValidator.isValid(secureCKey))
212                         throw new NotValidDataException("Not valid data for contextId and/or key");
213
214                 String response = null;
215                 SharedContext context = contextService.getSharedContext(context_id, ckey);
216                 if (context != null)
217                         response = "exists";
218
219                 return convertResponseToJSON(response);
220         }
221
222         /**
223          * Removes the specified key in the specified context (RESTful service
224          * method).
225          *
226          * @param request
227          *            HTTP servlet request
228          * @param context_id
229          *            ID that identifies the context, usually the ONAP Portal
230          *            session key.
231          * @param ckey
232          *            Key for the key-value pair to remove
233          * @return JSON with result indicating whether the context and key were
234          *         found.
235          * @throws Exception
236          *             on bad arguments
237          */
238         @ApiOperation(value = "Removes the specified key in the specified context.", response = SharedContextJsonResponse.class)
239         @RequestMapping(value = { "/remove" }, method = RequestMethod.GET, produces = "application/json")
240         public String removeContext(HttpServletRequest request, @RequestParam String context_id, @RequestParam String ckey)
241                         throws Exception {
242
243                 logger.debug(EELFLoggerDelegate.debugLogger, "removeContext for " + context_id + ", key " + ckey);
244                 if (context_id == null || ckey == null)
245                         throw new Exception("Received null for contextId and/or key");
246
247                 SecureString secureContextId = new SecureString(context_id);
248                 SecureString secureCKey = new SecureString(ckey);
249
250                 if (!dataValidator.isValid(secureContextId) || !dataValidator.isValid(secureCKey))
251                         throw new NotValidDataException("Not valid data for contextId and/or key");
252
253                 SharedContext context = contextService.getSharedContext(context_id, ckey);
254                 String response = null;
255                 if (context != null) {
256                         contextService.deleteSharedContext(context);
257                         response = "removed";
258                 }
259
260                 return convertResponseToJSON(response);
261         }
262
263         /**
264          * Clears all key-value pairs in the specified context (RESTful service
265          * method).
266          *
267          * @param request
268          *            HTTP servlet request
269          * @param context_id
270          *            ID that identifies the context, usually the ONAP Portal
271          *            session key.
272          * @return JSON with result indicating the number of key-value pairs
273          *         removed.
274          * @throws Exception
275          *             on bad arguments
276          */
277         @ApiOperation(value = "Clears all key-value pairs in the specified context.", response = SharedContextJsonResponse.class)
278         @RequestMapping(value = { "/clear" }, method = RequestMethod.GET, produces = "application/json")
279         public String clearContext(HttpServletRequest request, @RequestParam String context_id) throws Exception {
280
281                 logger.debug(EELFLoggerDelegate.debugLogger, "clearContext for " + context_id);
282                 if (context_id == null)
283                         throw new Exception("clearContext: Received null for contextId");
284
285                 SecureString secureContextId = new SecureString(context_id);
286
287                 if (!dataValidator.isValid(secureContextId))
288                         throw new NotValidDataException("Not valid data for contextId");
289
290                 int count = contextService.deleteSharedContexts(context_id);
291                 return convertResponseToJSON(Integer.toString(count));
292         }
293
294         /**
295          * Sets a context value for the specified context and key (RESTful service
296          * method). Creates the context if no context with the specified ID-key pair
297          * exists, overwrites the value if it exists already.
298          *
299          * @param request
300          *            HTTP servlet request
301          * @param userJson
302          *            JSON block with these tag-value pairs:
303          *            <UL>
304          *            <LI>context_id: ID that identifies the context
305          *            <LI>ckey: Key for the key-value pair to store
306          *            <LI>cvalue: Value to store
307          *            </UL>
308          * @return JSON with result indicating whether the value was added (key not
309          *         previously known) or replaced (key previously known).
310          * @throws Exception
311          *             on bad arguments
312          */
313         @ApiOperation(value = "Sets a context value for the specified context and key. Creates the context if no context with the specified ID-key pair exists, overwrites the value if it exists already.", response = SharedContextJsonResponse.class)
314         @RequestMapping(value = { "/set" }, method = RequestMethod.POST, produces = "application/json")
315         public String setContext(HttpServletRequest request, @RequestBody String userJson) throws Exception {
316                 if (userJson !=null){
317                 SecureString secureUserJson = new SecureString(userJson);
318                 if (!dataValidator.isValid(secureUserJson))
319                         throw new NotValidDataException("Not valid data for userJson");
320                 }
321
322                 @SuppressWarnings("unchecked")
323                 Map<String, Object> userData = mapper.readValue(userJson, Map.class);
324                 // Use column names as JSON tags
325                 final String contextId = (String) userData.get("context_id");
326                 final String key = (String) userData.get("ckey");
327                 final String value = (String) userData.get("cvalue");
328                 if (contextId == null || key == null)
329                         throw new Exception("setContext: received null for contextId and/or key");
330
331                 logger.debug(EELFLoggerDelegate.debugLogger, "setContext: ID " + contextId + ", key " + key + "->" + value);
332                 String response;
333                 SharedContext existing = contextService.getSharedContext(contextId, key);
334                 if (existing == null) {
335                         contextService.addSharedContext(contextId, key, value);
336                 } else {
337                         existing.setCvalue(value);
338                         contextService.saveSharedContext(existing);
339                 }
340                 response = existing == null ? "added" : "replaced";
341                 return convertResponseToJSON(response);
342         }
343
344         /**
345          * Creates a two-element JSON object tagged "response".
346          *
347          * @param responseBody
348          * @return JSON object as String
349          * @throws JsonProcessingException
350          */
351         private String convertResponseToJSON(String responseBody) throws JsonProcessingException {
352                 Map<String, String> responseMap = new HashMap<>();
353                 responseMap.put("response", responseBody);
354                 return mapper.writeValueAsString(responseMap);
355         }
356
357         /**
358          * Converts a list of SharedContext objects to a JSON array.
359          *
360          * @param contextList
361          * @return JSON array as String
362          * @throws JsonProcessingException
363          */
364         private String convertResponseToJSON(List<SharedContext> contextList) throws JsonProcessingException {
365                 return mapper.writeValueAsString(contextList);
366         }
367
368         /**
369          * Creates a JSON object with the content of the shared context; null is ok.
370          *
371          * @param context
372          * @return tag "response" with collection of context object's fields
373          * @throws JsonProcessingException
374          */
375         private String convertResponseToJSON(SharedContext context) throws JsonProcessingException {
376                 Map<String, Object> responseMap = new HashMap<>();
377                 responseMap.put("response", context);
378                 return mapper.writeValueAsString(responseMap);
379         }
380
381         /**
382          * Handles any exception thrown by a method in this controller.
383          *
384          * @param e
385          *            Exception
386          * @param response
387          *            HttpServletResponse
388          * @throws IOException
389          */
390         @ExceptionHandler(Exception.class)
391         protected void handleBadRequests(Exception e, HttpServletResponse response) throws IOException {
392                 logger.error(EELFLoggerDelegate.errorLogger, "handleBadRequest caught exception", e);
393                 response.sendError(HttpStatus.BAD_REQUEST.value(), e.getMessage());
394         }
395
396 }
397 class SharedContextJsonResponse {
398         String response;
399 }
400