Merge "Fix Feed Vulnerabilities"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / SubscriptionServlet.java
1 /*******************************************************************************\r
2  * ============LICENSE_START==================================================\r
3  * * org.onap.dmaap\r
4  * * ===========================================================================\r
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.\r
6  * * ===========================================================================\r
7  * * Licensed under the Apache License, Version 2.0 (the "License");\r
8  * * you may not use this file except in compliance with the License.\r
9  * * You may obtain a copy of the License at\r
10  * *\r
11  *  *      http://www.apache.org/licenses/LICENSE-2.0\r
12  * *\r
13  *  * Unless required by applicable law or agreed to in writing, software\r
14  * * distributed under the License is distributed on an "AS IS" BASIS,\r
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
16  * * See the License for the specific language governing permissions and\r
17  * * limitations under the License.\r
18  * * ============LICENSE_END====================================================\r
19  * *\r
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.\r
21  * *\r
22  ******************************************************************************/\r
23 \r
24 \r
25 package org.onap.dmaap.datarouter.provisioning;\r
26 \r
27 import java.io.IOException;\r
28 import java.io.InvalidObjectException;\r
29 import java.net.HttpURLConnection;\r
30 import java.net.URL;\r
31 import java.util.List;\r
32 import java.util.Vector;\r
33 \r
34 import javax.servlet.http.HttpServletRequest;\r
35 import javax.servlet.http.HttpServletResponse;\r
36 \r
37 import org.json.JSONException;\r
38 import org.json.JSONObject;\r
39 import org.onap.dmaap.datarouter.authz.AuthorizationResponse;\r
40 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;\r
41 import org.onap.dmaap.datarouter.provisioning.beans.Subscription;\r
42 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;\r
43 \r
44 import com.att.eelf.configuration.EELFLogger;\r
45 import com.att.eelf.configuration.EELFManager;\r
46 \r
47 import static org.onap.dmaap.datarouter.provisioning.utils.HttpServletUtils.sendResponseError;\r
48 \r
49 /**\r
50  * This servlet handles provisioning for the <subscriptionURL> which is generated by the provisioning server to\r
51  * handle the inspection, modification, and deletion of a particular subscription to a feed. It supports DELETE to\r
52  * delete a subscription, GET to retrieve information about the subscription, and PUT to modify the subscription.  In DR\r
53  * 3.0, POST is also supported in order to reset the subscription timers for individual subscriptions.\r
54  *\r
55  * @author Robert Eby\r
56  * @version $Id$\r
57  */\r
58 @SuppressWarnings("serial")\r
59 public class SubscriptionServlet extends ProxyServlet {\r
60 \r
61     public static final String SUBCNTRL_CONTENT_TYPE = "application/vnd.att-dr.subscription-control";\r
62     //Adding EELF Logger Rally:US664892\r
63     private static EELFLogger eelflogger = EELFManager.getInstance()\r
64         .getLogger("org.onap.dmaap.datarouter.provisioning.SubscriptionServlet");\r
65 \r
66     /**\r
67      * DELETE on the &lt;subscriptionUrl&gt; -- delete a subscription. See the <i>Deleting a Subscription</i> section in\r
68      * the <b>Provisioning API</b> document for details on how this method should be invoked.\r
69      */\r
70     @Override\r
71     public void doDelete(HttpServletRequest req, HttpServletResponse resp) {\r
72         setIpAndFqdnForEelf("doDelete");\r
73         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
74         EventLogRecord elr = new EventLogRecord(req);\r
75         String message = isAuthorizedForProvisioning(req);\r
76         if (message != null) {\r
77             elr.setMessage(message);\r
78             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
79             eventlogger.info(elr);\r
80             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
81             return;\r
82         }\r
83         if (isProxyServer()) {\r
84             super.doDelete(req, resp);\r
85             return;\r
86         }\r
87         String bhdr = req.getHeader(BEHALF_HEADER);\r
88         if (bhdr == null) {\r
89             message = "Missing " + BEHALF_HEADER + " header.";\r
90             elr.setMessage(message);\r
91             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
92             eventlogger.info(elr);\r
93             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
94             return;\r
95         }\r
96         int subid = getIdFromPath(req);\r
97         if (subid < 0) {\r
98             message = "Missing or bad subscription number.";\r
99             elr.setMessage(message);\r
100             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
101             eventlogger.info(elr);\r
102             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
103             return;\r
104         }\r
105         Subscription sub = Subscription.getSubscriptionById(subid);\r
106         if (sub == null) {\r
107             message = "Missing or bad subscription number.";\r
108             elr.setMessage(message);\r
109             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
110             eventlogger.info(elr);\r
111             sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, message, eventlogger);\r
112             return;\r
113         }\r
114         // Check with the Authorizer\r
115         AuthorizationResponse aresp = authz.decide(req);\r
116         if (!aresp.isAuthorized()) {\r
117             message = "Policy Engine disallows access.";\r
118             elr.setMessage(message);\r
119             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
120             eventlogger.info(elr);\r
121             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
122             return;\r
123         }\r
124 \r
125         // Delete Subscription\r
126         if (doDelete(sub)) {\r
127             activeSubs--;\r
128             // send response\r
129             elr.setResult(HttpServletResponse.SC_NO_CONTENT);\r
130             eventlogger.info(elr);\r
131             resp.setStatus(HttpServletResponse.SC_NO_CONTENT);\r
132             provisioningDataChanged();\r
133         } else {\r
134             // Something went wrong with the DELETE\r
135             elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
136             eventlogger.info(elr);\r
137             sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, intlogger);\r
138         }\r
139     }\r
140 \r
141     /**\r
142      * GET on the &lt;subscriptionUrl&gt; -- get information about a subscription. See the <i>Retreiving Information\r
143      * about a Subscription</i> section in the <b>Provisioning API</b> document for details on how this method should be\r
144      * invoked.\r
145      */\r
146     @Override\r
147     public void doGet(HttpServletRequest req, HttpServletResponse resp) {\r
148         setIpAndFqdnForEelf("doGet");\r
149         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
150         EventLogRecord elr = new EventLogRecord(req);\r
151         String message = isAuthorizedForProvisioning(req);\r
152         if (message != null) {\r
153             elr.setMessage(message);\r
154             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
155             eventlogger.info(elr);\r
156             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
157             return;\r
158         }\r
159         if (isProxyServer()) {\r
160             super.doGet(req, resp);\r
161             return;\r
162         }\r
163         String bhdr = req.getHeader(BEHALF_HEADER);\r
164         if (bhdr == null) {\r
165             message = "Missing " + BEHALF_HEADER + " header.";\r
166             elr.setMessage(message);\r
167             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
168             eventlogger.info(elr);\r
169             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
170             return;\r
171         }\r
172         int subid = getIdFromPath(req);\r
173         if (subid < 0) {\r
174             message = "Missing or bad subscription number.";\r
175             elr.setMessage(message);\r
176             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
177             eventlogger.info(elr);\r
178             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
179             return;\r
180         }\r
181         Subscription sub = Subscription.getSubscriptionById(subid);\r
182         if (sub == null) {\r
183             message = "Missing or bad subscription number.";\r
184             elr.setMessage(message);\r
185             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
186             eventlogger.info(elr);\r
187             sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, message, eventlogger);\r
188             return;\r
189         }\r
190         // Check with the Authorizer\r
191         AuthorizationResponse aresp = authz.decide(req);\r
192         if (!aresp.isAuthorized()) {\r
193             message = "Policy Engine disallows access.";\r
194             elr.setMessage(message);\r
195             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
196             eventlogger.info(elr);\r
197             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
198             return;\r
199         }\r
200 \r
201         // send response\r
202         elr.setResult(HttpServletResponse.SC_OK);\r
203         eventlogger.info(elr);\r
204         resp.setStatus(HttpServletResponse.SC_OK);\r
205         resp.setContentType(SUBFULL_CONTENT_TYPE);\r
206         try {\r
207             resp.getOutputStream().print(sub.asJSONObject(true).toString());\r
208         } catch (IOException ioe) {\r
209             eventlogger.error("IOException: " + ioe.getMessage());\r
210         }\r
211     }\r
212 \r
213     /**\r
214      * PUT on the &lt;subscriptionUrl&gt; -- modify a subscription. See the <i>Modifying a Subscription</i> section in\r
215      * the <b>Provisioning API</b> document for details on how this method should be invoked.\r
216      */\r
217     @Override\r
218     public void doPut(HttpServletRequest req, HttpServletResponse resp) {\r
219         setIpAndFqdnForEelf("doPut");\r
220         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_SUBID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");\r
221         EventLogRecord elr = new EventLogRecord(req);\r
222         String message = isAuthorizedForProvisioning(req);\r
223         if (message != null) {\r
224             elr.setMessage(message);\r
225             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
226             eventlogger.info(elr);\r
227             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
228             return;\r
229         }\r
230         if (isProxyServer()) {\r
231             super.doPut(req, resp);\r
232             return;\r
233         }\r
234         String bhdr = req.getHeader(BEHALF_HEADER);\r
235         if (bhdr == null) {\r
236             message = "Missing " + BEHALF_HEADER + " header.";\r
237             elr.setMessage(message);\r
238             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
239             eventlogger.info(elr);\r
240             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
241             return;\r
242         }\r
243         int subid = getIdFromPath(req);\r
244         if (subid < 0) {\r
245             message = "Missing or bad subscription number.";\r
246             elr.setMessage(message);\r
247             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
248             eventlogger.info(elr);\r
249             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
250             return;\r
251         }\r
252         Subscription oldsub = Subscription.getSubscriptionById(subid);\r
253         if (oldsub == null) {\r
254             message = "Missing or bad subscription number.";\r
255             elr.setMessage(message);\r
256             elr.setResult(HttpServletResponse.SC_NOT_FOUND);\r
257             eventlogger.info(elr);\r
258             sendResponseError(resp, HttpServletResponse.SC_NOT_FOUND, message, eventlogger);\r
259             return;\r
260         }\r
261         // Check with the Authorizer\r
262         AuthorizationResponse aresp = authz.decide(req);\r
263         if (!aresp.isAuthorized()) {\r
264             message = "Policy Engine disallows access.";\r
265             elr.setMessage(message);\r
266             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
267             eventlogger.info(elr);\r
268             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
269             return;\r
270         }\r
271         // check content type is SUB_CONTENT_TYPE, version 1.0\r
272         ContentHeader ch = getContentHeader(req);\r
273         String ver = ch.getAttribute("version");\r
274         if (!ch.getType().equals(SUB_BASECONTENT_TYPE) || !(ver.equals("1.0") || ver.equals("2.0"))) {\r
275             message = "Incorrect content-type";\r
276             elr.setMessage(message);\r
277             elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
278             eventlogger.info(elr);\r
279             sendResponseError(resp, HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, eventlogger);\r
280             return;\r
281         }\r
282         JSONObject jo = getJSONfromInput(req);\r
283         if (jo == null) {\r
284             message = "Badly formed JSON";\r
285             elr.setMessage(message);\r
286             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
287             eventlogger.info(elr);\r
288             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
289             return;\r
290         }\r
291         if (intlogger.isDebugEnabled()) {\r
292             intlogger.debug(jo.toString());\r
293         }\r
294         Subscription sub = null;\r
295         try {\r
296             sub = new Subscription(jo);\r
297         } catch (InvalidObjectException e) {\r
298             message = e.getMessage();\r
299             elr.setMessage(message);\r
300             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
301             eventlogger.info(elr);\r
302             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
303             return;\r
304         }\r
305         sub.setSubid(oldsub.getSubid());\r
306         sub.setFeedid(oldsub.getFeedid());\r
307         sub.setSubscriber(bhdr);    // set from X-ATT-DR-ON-BEHALF-OF header\r
308 \r
309         String subjectgroup = (req.getHeader("X-ATT-DR-ON-BEHALF-OF-GROUP")); //Adding for group feature:Rally US708115\r
310         if (!oldsub.getSubscriber().equals(sub.getSubscriber()) && subjectgroup == null) {\r
311             message = "This subscriber must be modified by the same subscriber that created it.";\r
312             elr.setMessage(message);\r
313             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
314             eventlogger.info(elr);\r
315             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
316             return;\r
317         }\r
318 \r
319         // Update SUBSCRIPTIONS table entries\r
320         if (doUpdate(sub)) {\r
321             // send response\r
322             elr.setResult(HttpServletResponse.SC_OK);\r
323             eventlogger.info(elr);\r
324             resp.setStatus(HttpServletResponse.SC_OK);\r
325             resp.setContentType(SUBFULL_CONTENT_TYPE);\r
326             try {\r
327                 resp.getOutputStream().print(sub.asLimitedJSONObject().toString());\r
328             } catch (IOException ioe) {\r
329                 eventlogger.error("IOException: " + ioe.getMessage());\r
330             }\r
331 \r
332             /**Change Owner ship of Subscriber     Adding for group feature:Rally US708115*/\r
333             if (jo.has("changeowner") && subjectgroup != null) {\r
334                 try {\r
335                     Boolean changeowner = (Boolean) jo.get("changeowner");\r
336                     if (changeowner != null && changeowner.equals(true)) {\r
337                         sub.setSubscriber(req.getHeader(BEHALF_HEADER));\r
338                         sub.changeOwnerShip();\r
339                     }\r
340                 } catch (JSONException je) {\r
341                     eventlogger.error("JSONException: " + je.getMessage());\r
342                 }\r
343             }\r
344             /***End of change ownership*/\r
345 \r
346             provisioningDataChanged();\r
347         } else {\r
348             // Something went wrong with the UPDATE\r
349             elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);\r
350             eventlogger.info(elr);\r
351             sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, intlogger);\r
352         }\r
353     }\r
354 \r
355     /**\r
356      * POST on the &lt;subscriptionUrl&gt; -- control a subscription. See the <i>Resetting a Subscription's Retry\r
357      * Schedule</i> section in the <b>Provisioning API</b> document for details on how this method should be invoked.\r
358      */\r
359     @Override\r
360     public void doPost(HttpServletRequest req, HttpServletResponse resp) {\r
361 // OLD pre-3.0 code\r
362 //        String message = "POST not allowed for the subscriptionURL.";\r
363 //        EventLogRecord elr = new EventLogRecord(req);\r
364 //        elr.setMessage(message);\r
365 //        elr.setResult(HttpServletResponse.SC_METHOD_NOT_ALLOWED);\r
366 //        eventlogger.info(elr);\r
367 //        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, message);\r
368 \r
369         setIpAndFqdnForEelf("doPost");\r
370         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));\r
371         EventLogRecord elr = new EventLogRecord(req);\r
372         String message = isAuthorizedForProvisioning(req);\r
373         if (message != null) {\r
374             elr.setMessage(message);\r
375             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
376             eventlogger.info(elr);\r
377             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
378             return;\r
379         }\r
380         if (isProxyServer()) {\r
381             super.doPost(req, resp);\r
382             return;\r
383         }\r
384         String bhdr = req.getHeader(BEHALF_HEADER);\r
385         if (bhdr == null) {\r
386             message = "Missing " + BEHALF_HEADER + " header.";\r
387             elr.setMessage(message);\r
388             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
389             eventlogger.info(elr);\r
390             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
391             return;\r
392         }\r
393         final int subid = getIdFromPath(req);\r
394         if (subid < 0 || Subscription.getSubscriptionById(subid) == null) {\r
395             message = "Missing or bad subscription number.";\r
396             elr.setMessage(message);\r
397             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
398             eventlogger.info(elr);\r
399             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
400             return;\r
401         }\r
402         // check content type is SUBCNTRL_CONTENT_TYPE, version 1.0\r
403         ContentHeader ch = getContentHeader(req);\r
404         String ver = ch.getAttribute("version");\r
405         if (!ch.getType().equals(SUBCNTRL_CONTENT_TYPE) || !ver.equals("1.0")) {\r
406             message = "Incorrect content-type";\r
407             elr.setMessage(message);\r
408             elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);\r
409             eventlogger.info(elr);\r
410             sendResponseError(resp, HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, message, eventlogger);\r
411             return;\r
412         }\r
413         // Check with the Authorizer\r
414         AuthorizationResponse aresp = authz.decide(req);\r
415         if (!aresp.isAuthorized()) {\r
416             message = "Policy Engine disallows access.";\r
417             elr.setMessage(message);\r
418             elr.setResult(HttpServletResponse.SC_FORBIDDEN);\r
419             eventlogger.info(elr);\r
420             sendResponseError(resp, HttpServletResponse.SC_FORBIDDEN, message, eventlogger);\r
421             return;\r
422         }\r
423         JSONObject jo = getJSONfromInput(req);\r
424         if (jo == null) {\r
425             message = "Badly formed JSON";\r
426             elr.setMessage(message);\r
427             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
428             eventlogger.info(elr);\r
429             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
430             return;\r
431         }\r
432         try {\r
433             // Only the active POD sends notifications\r
434             boolean active = SynchronizerTask.getSynchronizer().isActive();\r
435             boolean b = jo.getBoolean("failed");\r
436             if (active && !b) {\r
437                 // Notify all nodes to reset the subscription\r
438                 SubscriberNotifyThread t = new SubscriberNotifyThread();\r
439                 t.resetSubscription(subid);\r
440                 t.start();\r
441             }\r
442             // send response\r
443             elr.setResult(HttpServletResponse.SC_ACCEPTED);\r
444             eventlogger.info(elr);\r
445             resp.setStatus(HttpServletResponse.SC_ACCEPTED);\r
446         } catch (JSONException e) {\r
447             message = "Badly formed JSON";\r
448             elr.setMessage(message);\r
449             elr.setResult(HttpServletResponse.SC_BAD_REQUEST);\r
450             eventlogger.info(elr);\r
451             sendResponseError(resp, HttpServletResponse.SC_BAD_REQUEST, message, eventlogger);\r
452         }\r
453     }\r
454 \r
455     /**\r
456      * A Thread class used to serially send reset notifications to all nodes in the DR network, when a POST is received\r
457      * for a subscription.\r
458      */\r
459     public class SubscriberNotifyThread extends Thread {\r
460 \r
461         public static final String URL_TEMPLATE = "http://%s/internal/resetSubscription/%d";\r
462         private List<String> urls = new Vector<String>();\r
463 \r
464         public SubscriberNotifyThread() {\r
465             setName("SubscriberNotifyThread");\r
466         }\r
467 \r
468         public void resetSubscription(int subid) {\r
469             for (String nodename : BaseServlet.getNodes()) {\r
470                 String u = String.format(URL_TEMPLATE, nodename, subid);\r
471                 urls.add(u);\r
472             }\r
473         }\r
474 \r
475         public void run() {\r
476             try {\r
477                 while (!urls.isEmpty()) {\r
478                     String u = urls.remove(0);\r
479                     try {\r
480                         URL url = new URL(u);\r
481                         HttpURLConnection conn = (HttpURLConnection) url.openConnection();\r
482                         conn.connect();\r
483                         conn.getContentLength();    // Force the GET through\r
484                         conn.disconnect();\r
485                     } catch (IOException e) {\r
486                         intlogger.info("IOException Error accessing URL: " + u + ": " + e.getMessage());\r
487                     }\r
488                 }\r
489             } catch (Exception e) {\r
490                 intlogger.warn("Caught exception in SubscriberNotifyThread: " + e);\r
491             }\r
492         }\r
493     }\r
494 }\r