f1fa54c70b4673fe444422b90009682c167ebef9
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / beans / IngressRoute.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.beans;\r
26 \r
27 import com.att.eelf.configuration.EELFLogger;\r
28 import com.att.eelf.configuration.EELFManager;\r
29 import java.net.InetAddress;\r
30 import java.net.UnknownHostException;\r
31 import java.sql.Connection;\r
32 import java.sql.PreparedStatement;\r
33 import java.sql.ResultSet;\r
34 import java.sql.SQLException;\r
35 import java.sql.Statement;\r
36 import java.util.Collection;\r
37 import java.util.Set;\r
38 import java.util.SortedSet;\r
39 import java.util.TreeSet;\r
40 import javax.servlet.http.HttpServletRequest;\r
41 import org.apache.commons.codec.binary.Base64;\r
42 import org.json.JSONArray;\r
43 import org.json.JSONObject;\r
44 import org.onap.dmaap.datarouter.provisioning.utils.DB;\r
45 \r
46 /**\r
47  * The representation of one route in the Ingress Route Table.\r
48  *\r
49  * @author Robert P. Eby\r
50  * @version $Id: IngressRoute.java,v 1.3 2013/12/16 20:30:23 eby Exp $\r
51  */\r
52 \r
53 public class IngressRoute extends NodeClass implements Comparable<IngressRoute> {\r
54 \r
55     private static final String NODESET = "NODESET";\r
56     private static EELFLogger intlogger = EELFManager.getInstance().getLogger("InternalLog");\r
57     private static final String SQLEXCEPTION = "SQLException: ";\r
58     private final int seq;\r
59     private final int feedid;\r
60     private final String userid;\r
61     private final String subnet;\r
62     private int nodelist;\r
63     private SortedSet<String> nodes;\r
64 \r
65     /**\r
66      * Ingress route constructor.\r
67      * @param seq squence number\r
68      * @param feedid id for feed\r
69      * @param user user name\r
70      * @param subnet subnet string\r
71      * @param nodes collection of nodes\r
72      */\r
73     public IngressRoute(int seq, int feedid, String user, String subnet, Collection<String> nodes) {\r
74         this(seq, feedid, user, subnet);\r
75         this.nodelist = -1;\r
76         this.nodes = new TreeSet<>(nodes);\r
77     }\r
78 \r
79     private IngressRoute(int seq, int feedid, String user, String subnet, int nodeset) {\r
80         this(seq, feedid, user, subnet);\r
81         this.nodelist = nodeset;\r
82         this.nodes = new TreeSet<>(readNodes());\r
83     }\r
84 \r
85     private IngressRoute(int seq, int feedid, String user, String subnet) {\r
86         this.seq = seq;\r
87         this.feedid = feedid;\r
88         this.userid = (user == null) ? "-" : user;\r
89         this.subnet = (subnet == null) ? "-" : subnet;\r
90         this.nodelist = -1;\r
91         this.nodes = null;\r
92         if (Feed.getFeedById(feedid) == null) {\r
93             throw new IllegalArgumentException("No such feed: " + feedid);\r
94         }\r
95         if (!"-".equals(this.subnet)) {\r
96             SubnetMatcher sm = new SubnetMatcher(subnet);\r
97             if (!sm.isValid()) {\r
98                 throw new IllegalArgumentException("Invalid subnet: " + subnet);\r
99             }\r
100         }\r
101     }\r
102 \r
103     /**\r
104      * Ingress route constructor.\r
105      * @param jo JSONObject\r
106      */\r
107     public IngressRoute(JSONObject jo) {\r
108         this.seq = jo.optInt("seq");\r
109         this.feedid = jo.optInt("feedid");\r
110         String user = jo.optString("user");\r
111         this.userid = "".equals(user) ? "-" : user;\r
112         user = jo.optString("subnet");\r
113         this.subnet = "".equals(user) ? "-" : user;\r
114         this.nodelist = -1;\r
115         this.nodes = new TreeSet<>();\r
116         JSONArray ja = jo.getJSONArray("node");\r
117         for (int i = 0; i < ja.length(); i++) {\r
118             this.nodes.add(ja.getString(i));\r
119         }\r
120     }\r
121 \r
122 \r
123     /**\r
124      * Get all IngressRoutes in the database, sorted in order according to their sequence field.\r
125      *\r
126      * @return a sorted set of IngressRoutes\r
127      */\r
128     public static SortedSet<IngressRoute> getAllIngressRoutes() {\r
129         return getAllIngressRoutesForSQL("select SEQUENCE, FEEDID, USERID, SUBNET, NODESET from INGRESS_ROUTES");\r
130     }\r
131 \r
132     /**\r
133      * Get all IngressRoutes in the database with a particular sequence number.\r
134      *\r
135      * @param seq the sequence number\r
136      * @return a set of IngressRoutes\r
137      */\r
138     public static Set<IngressRoute> getIngressRoutesForSeq(int seq) {\r
139         return getAllIngressRoutesForSQL(\r
140                 "select SEQUENCE, FEEDID, USERID, SUBNET, NODESET from INGRESS_ROUTES where SEQUENCE = " + seq);\r
141     }\r
142 \r
143     private static SortedSet<IngressRoute> getAllIngressRoutesForSQL(String sql) {\r
144         SortedSet<IngressRoute> set = new TreeSet<>();\r
145         try {\r
146             DB db = new DB();\r
147             @SuppressWarnings("resource")\r
148             Connection conn = db.getConnection();\r
149             try (Statement stmt = conn.createStatement()) {\r
150                 try (ResultSet rs = stmt.executeQuery(sql)) {\r
151                     addIngressRouteToSet(set, rs);\r
152                 }\r
153             }\r
154             db.release(conn);\r
155         } catch (SQLException e) {\r
156             intlogger.error("PROV0001 getAllIngressRoutesForSQL: " + e.getMessage(), e);\r
157         }\r
158         return set;\r
159     }\r
160 \r
161     private static void addIngressRouteToSet(SortedSet<IngressRoute> set, ResultSet rs) throws SQLException {\r
162         while (rs.next()) {\r
163             int seq = rs.getInt("SEQUENCE");\r
164             int feedid = rs.getInt("FEEDID");\r
165             String user = rs.getString("USERID");\r
166             String subnet = rs.getString("SUBNET");\r
167             int nodeset = rs.getInt(NODESET);\r
168             set.add(new IngressRoute(seq, feedid, user, subnet, nodeset));\r
169         }\r
170     }\r
171 \r
172     /**\r
173      * Get the maximum node set ID in use in the DB.\r
174      *\r
175      * @return the integer value of the maximum\r
176      */\r
177     private static int getMaxNodeSetID() {\r
178         return getMax("select max(SETID) as MAX from NODESETS");\r
179     }\r
180 \r
181     /**\r
182      * Get the maximum node sequence number in use in the DB.\r
183      *\r
184      * @return the integer value of the maximum\r
185      */\r
186     public static int getMaxSequence() {\r
187         return getMax("select max(SEQUENCE) as MAX from INGRESS_ROUTES");\r
188     }\r
189 \r
190     private static int getMax(String sql) {\r
191         int rv = 0;\r
192         DB db = new DB();\r
193         try (Connection conn = db.getConnection();\r
194                 Statement stmt = conn.createStatement()) {\r
195             try (ResultSet rs = stmt.executeQuery(sql)) {\r
196                 if (rs.next()) {\r
197                     rv = rs.getInt("MAX");\r
198                 }\r
199             }\r
200             db.release(conn);\r
201         } catch (SQLException e) {\r
202             intlogger.error("PROV0002 getMax: " + e.getMessage(), e);\r
203         }\r
204         return rv;\r
205     }\r
206 \r
207     /**\r
208      * Get an Ingress Route for a particular feed ID, user, and subnet.\r
209      *\r
210      * @param feedid the Feed ID to look for\r
211      * @param user the user name to look for\r
212      * @param subnet the subnet to look for\r
213      * @return the Ingress Route, or null of there is none\r
214      */\r
215     public static IngressRoute getIngressRoute(int feedid, String user, String subnet) {\r
216         IngressRoute ir = null;\r
217         DB db = new DB();\r
218         String sql = "select SEQUENCE, NODESET from INGRESS_ROUTES where FEEDID = ? AND USERID = ? and SUBNET = ?";\r
219         try (Connection conn = db.getConnection();\r
220                 PreparedStatement ps = conn.prepareStatement(sql)) {\r
221             ps.setInt(1, feedid);\r
222             ps.setString(2, user);\r
223             ps.setString(3, subnet);\r
224             try (ResultSet rs = ps.executeQuery()) {\r
225                 if (rs.next()) {\r
226                     int seq = rs.getInt("SEQUENCE");\r
227                     int nodeset = rs.getInt(NODESET);\r
228                     ir = new IngressRoute(seq, feedid, user, subnet, nodeset);\r
229                 }\r
230             }\r
231             db.release(conn);\r
232         } catch (SQLException e) {\r
233             intlogger.error("PROV0003 getIngressRoute: " + e.getMessage(), e);\r
234         }\r
235         return ir;\r
236     }\r
237 \r
238     /**\r
239      * Does this particular IngressRoute match a request, represented by feedid and req? To match, <i>feedid</i> must\r
240      * match the feed ID in the route, the user in the route (if specified) must match the user in the request, and the\r
241      * subnet in the route (if specified) must match the subnet from the request.\r
242      *\r
243      * @param feedid the feedid for this request\r
244      * @param req the remainder of the request\r
245      * @return true if a match, false otherwise\r
246      */\r
247     public boolean matches(int feedid, HttpServletRequest req) {\r
248         // Check feedid\r
249         if (this.feedid != feedid) {\r
250             return false;\r
251         }\r
252         // Get user from request and compare\r
253         // Note: we don't check the password; the node will do that\r
254         if (userid.length() > 0 && !"-".equals(userid)) {\r
255             String credentials = req.getHeader("Authorization");\r
256             if (credentials == null || !credentials.startsWith("Basic ")) {\r
257                 return false;\r
258             }\r
259             String cred = new String(Base64.decodeBase64(credentials.substring(6)));\r
260             int ix = cred.indexOf(':');\r
261             if (ix >= 0) {\r
262                 cred = cred.substring(0, ix);\r
263             }\r
264             if (!cred.equals(this.userid)) {\r
265                 return false;\r
266             }\r
267         }\r
268         // If this route has a subnet, match it against the requester's IP addr\r
269         if (subnet.length() > 0 && !"-".equals(subnet)) {\r
270             try {\r
271                 InetAddress inet = InetAddress.getByName(req.getRemoteAddr());\r
272                 SubnetMatcher sm = new SubnetMatcher(subnet);\r
273                 return sm.matches(inet.getAddress());\r
274             } catch (UnknownHostException e) {\r
275                 intlogger.error("PROV0008 matches: " + e.getMessage(), e);\r
276                 return false;\r
277             }\r
278         }\r
279         return true;\r
280     }\r
281 \r
282     /**\r
283      * Compare IP addresses as byte arrays to a subnet specified as a CIDR. Taken from\r
284      * org.onap.dmaap.datarouter.node.SubnetMatcher and modified somewhat.\r
285      */\r
286     public class SubnetMatcher {\r
287 \r
288         private byte[] sn;\r
289         private int len;\r
290         private int mask;\r
291         private boolean valid;\r
292 \r
293         /**\r
294          * Construct a subnet matcher given a CIDR.\r
295          *\r
296          * @param subnet The CIDR to match\r
297          */\r
298         public SubnetMatcher(String subnet) {\r
299             int index = subnet.lastIndexOf('/');\r
300             if (index == -1) {\r
301                 try {\r
302                     sn = InetAddress.getByName(subnet).getAddress();\r
303                     len = sn.length;\r
304                     valid = true;\r
305                 } catch (UnknownHostException e) {\r
306                     intlogger.error("PROV0008 SubnetMatcher: " + e.getMessage(), e);\r
307                     len = 0;\r
308                     valid = false;\r
309                 }\r
310                 mask = 0;\r
311             } else {\r
312                 int num = Integer.parseInt(subnet.substring(index + 1));\r
313                 try {\r
314                     sn = InetAddress.getByName(subnet.substring(0, index)).getAddress();\r
315                     valid = true;\r
316                 } catch (UnknownHostException e) {\r
317                     intlogger.error("PROV0008 SubnetMatcher: " + e.getMessage(), e);\r
318                     valid = false;\r
319                 }\r
320                 len = num / 8;\r
321                 mask = ((0xff00) >> (num % 8)) & 0xff;\r
322             }\r
323         }\r
324 \r
325         boolean isValid() {\r
326             return valid;\r
327         }\r
328 \r
329         /**\r
330          * Is the IP address in the CIDR?.\r
331          *\r
332          * @param addr the IP address as bytes in network byte order\r
333          * @return true if the IP address matches.\r
334          */\r
335         boolean matches(byte[] addr) {\r
336             if (!valid || (addr.length != sn.length)) {\r
337                 return false;\r
338             }\r
339             for (int i = 0; i < len; i++) {\r
340                 if (addr[i] != sn[i]) {\r
341                     return false;\r
342                 }\r
343             }\r
344             if (mask != 0 && ((addr[len] ^ sn[len]) & mask) != 0) {\r
345                 return false;\r
346             }\r
347             return true;\r
348         }\r
349     }\r
350 \r
351     /**\r
352      * Get the list of node names for this route.\r
353      *\r
354      * @return the list\r
355      */\r
356     public SortedSet<String> getNodes() {\r
357         return this.nodes;\r
358     }\r
359 \r
360     private Collection<String> readNodes() {\r
361         Collection<String> set = new TreeSet<>();\r
362         DB db = new DB();\r
363         String sql = "select NODEID from NODESETS where SETID = ?";\r
364         try (Connection conn = db.getConnection()) {\r
365             try (PreparedStatement ps = conn.prepareStatement(sql)) {\r
366                 ps.setInt(1, nodelist);\r
367                 addNodeToSet(set, ps);\r
368             }\r
369             db.release(conn);\r
370         } catch (SQLException e) {\r
371             intlogger.error(SQLEXCEPTION + e.getMessage(), e);\r
372         }\r
373         return set;\r
374     }\r
375 \r
376     private void addNodeToSet(Collection<String> set, PreparedStatement ps) throws SQLException {\r
377         try (ResultSet rs = ps.executeQuery()) {\r
378             while (rs.next()) {\r
379                 int id = rs.getInt("NODEID");\r
380                 set.add(lookupNodeID(id));\r
381             }\r
382         }\r
383     }\r
384 \r
385     /**\r
386      * Delete the IRT route having this IngressRoutes feed ID, user ID, and subnet from the database.\r
387      *\r
388      * @return true if the delete succeeded\r
389      */\r
390     @Override\r
391     public boolean doDelete(Connection conn) {\r
392         boolean rv = true;\r
393         try (PreparedStatement ps = conn.prepareStatement(\r
394                  "delete from INGRESS_ROUTES where FEEDID = ? and USERID = ? and SUBNET = ?");\r
395                 PreparedStatement ps2 = conn.prepareStatement("delete from NODESETS where SETID = ?")) {\r
396             // Delete the Ingress Route\r
397             ps.setInt(1, feedid);\r
398             ps.setString(2, userid);\r
399             ps.setString(3, subnet);\r
400             ps.execute();\r
401             ps.close();\r
402             // Delete the NodeSet\r
403             ps2.setInt(1, nodelist);\r
404             ps2.execute();\r
405         } catch (SQLException e) {\r
406             rv = false;\r
407             intlogger.warn("PROV0007 doDelete: " + e.getMessage(), e);\r
408         }\r
409         return rv;\r
410     }\r
411 \r
412     @Override\r
413     public boolean doInsert(Connection conn) {\r
414         boolean rv = false;\r
415         try (PreparedStatement ps = conn.prepareStatement("insert into NODESETS (SETID, NODEID) values (?,?)");\r
416                 PreparedStatement ps2 = conn.prepareStatement("insert into INGRESS_ROUTES (SEQUENCE, FEEDID, USERID,"\r
417                         + " SUBNET, NODESET) values (?, ?, ?, ?, ?)")) {\r
418             // Create the NODESETS rows & set nodelist\r
419             this.nodelist = getMaxNodeSetID() + 1;\r
420             for (String node : nodes) {\r
421                 int id = lookupNodeName(node);\r
422                 ps.setInt(1, this.nodelist);\r
423                 ps.setInt(2, id);\r
424                 ps.execute();\r
425             }\r
426             // Create the INGRESS_ROUTES row\r
427             ps2.setInt(1, this.seq);\r
428             ps2.setInt(2, this.feedid);\r
429             ps2.setString(3, this.userid);\r
430             ps2.setString(4, this.subnet);\r
431             ps2.setInt(5, this.nodelist);\r
432             ps2.execute();\r
433             rv = true;\r
434         } catch (SQLException e) {\r
435             intlogger.warn("PROV0005 doInsert: " + e.getMessage(), e);\r
436         }\r
437         return rv;\r
438     }\r
439 \r
440     @Override\r
441     public boolean doUpdate(Connection conn) {\r
442         return doDelete(conn) && doInsert(conn);\r
443     }\r
444 \r
445     @Override\r
446     public JSONObject asJSONObject() {\r
447         JSONObject jo = new JSONObject();\r
448         jo.put("feedid", feedid);\r
449         // Note: for user and subnet, null, "", and "-" are equivalent\r
450         if (userid != null && !"-".equals(userid) && !"".equals(userid)) {\r
451             jo.put("user", userid);\r
452         }\r
453         if (subnet != null && !"-".equals(subnet) && !"".equals(subnet)) {\r
454             jo.put("subnet", subnet);\r
455         }\r
456         jo.put("seq", seq);\r
457         jo.put("node", nodes);\r
458         return jo;\r
459     }\r
460 \r
461     @Override\r
462     public String getKey() {\r
463         return String\r
464                 .format("%d/%s/%s/%d", feedid, (userid == null) ? "" : userid, (subnet == null) ? "" : subnet, seq);\r
465     }\r
466 \r
467     @Override\r
468     public int hashCode() {\r
469         return toString().hashCode();\r
470     }\r
471 \r
472     @Override\r
473     public boolean equals(Object obj) {\r
474         if (!(obj instanceof IngressRoute)) {\r
475             return false;\r
476         }\r
477         return this.compareTo((IngressRoute) obj) == 0;\r
478     }\r
479 \r
480     @Override\r
481     public int compareTo(IngressRoute in) {\r
482         if (in == null) {\r
483             throw new NullPointerException();\r
484         }\r
485         int num = this.feedid - in.feedid;\r
486         if (num != 0) {\r
487             return num;\r
488         }\r
489         num = this.seq - in.seq;\r
490         if (num != 0) {\r
491             return num;\r
492         }\r
493         num = this.userid.compareTo(in.userid);\r
494         if (num != 0) {\r
495             return num;\r
496         }\r
497         num = this.subnet.compareTo(in.subnet);\r
498         if (num != 0) {\r
499             return num;\r
500         }\r
501         return this.nodes.equals(in.nodes) ? 0 : 1;\r
502     }\r
503 \r
504     @Override\r
505     public String toString() {\r
506         return String.format("INGRESS: feed=%d, userid=%s, subnet=%s, seq=%d", feedid, (userid == null) ? "" : userid,\r
507                 (subnet == null) ? "" : subnet, seq);\r
508     }\r
509 }\r