Add guard junits
[policy/drools-applications.git] / controlloop / common / guard / src / main / java / org / onap / policy / guard / PolicyGuardXacmlHelper.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * guard
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.guard;
22
23 import java.io.BufferedReader;
24 import java.io.ByteArrayInputStream;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.Serializable;
29 import java.net.HttpURLConnection;
30 import java.net.URL;
31 import java.util.ArrayList;
32 import java.util.Base64;
33 import java.util.Iterator;
34 import java.util.Properties;
35 import java.util.UUID;
36
37 import org.apache.commons.io.IOUtils;
38 import org.apache.http.entity.ContentType;
39 import org.json.JSONObject;
40 import org.onap.policy.drools.system.PolicyEngine;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.att.research.xacml.api.Attribute;
45 import com.att.research.xacml.api.AttributeCategory;
46 import com.att.research.xacml.api.AttributeValue;
47 import com.att.research.xacml.api.Result;
48
49
50 public class PolicyGuardXacmlHelper {
51
52         private static final Logger logger = LoggerFactory
53                         .getLogger(PolicyGuardXacmlHelper.class);
54         
55         private static final Logger netLogger = LoggerFactory.getLogger(org.onap.policy.drools.event.comm.Topic.NETWORK_LOGGER);
56
57         public PolicyGuardXacmlHelper() {
58                 init(PolicyEngine.manager.getEnvironment());
59         }
60
61         // initialized from 'pdpx.url' property --
62         // Each entry in 'restUrls' contains a destination URL, and an optional
63         // 'Authorization' header entry. 'restUrlIndex' indicates the next
64         // entry to try -- after each failure, the index is advanced to the
65         // next entry (wrapping to the beginning, if needed).
66         static private class URLEntry implements Serializable {
67                 URL restURL;
68                 String authorization = null;
69                 String clientAuth = null;
70                 String environment = null;
71         };
72
73         private URLEntry[] restUrls = null;
74         private int restUrlIndex = 0;
75
76         // REST timeout, initialized from 'pdpx.timeout' property
77         private int timeout = 20000;
78
79
80         // initialized from 'guard.disabled', but may also be set to 'true' if
81         // there is an initialization error
82         private boolean disabled = false;
83
84         // errors that forced 'disabled' to be set to 'true'
85         private String errorMessage = null;
86
87         public String callPDP(PolicyGuardXacmlRequestAttributes xacmlReq) {
88                 //
89                 // Send it to the PDP
90                 //
91                 String response = null;
92
93                 //
94                 // Build the json request
95                 //
96                 JSONObject attributes = new JSONObject();
97                 attributes.put("actor", xacmlReq.getActor_id());
98                 attributes.put("recipe", xacmlReq.getOperation_id());
99                 attributes.put("target", xacmlReq.getTarget_id());
100                 if (xacmlReq.getClname_id() != null) {
101                         attributes.put("clname", xacmlReq.getClname_id());
102                 }
103                 JSONObject jsonReq = new JSONObject();
104                 jsonReq.put("decisionAttributes", attributes);
105                 jsonReq.put("onapName", "PDPD");
106
107
108                 try {
109                         //
110                         // Call RESTful PDP
111                         //
112                         URLEntry urlEntry = restUrls[restUrlIndex];
113                         netLogger.info("[OUT|{}|{}|]{}{}", "GUARD", urlEntry.restURL, System.lineSeparator(), jsonReq.toString());
114                         response = callRESTfulPDP(new ByteArrayInputStream(jsonReq
115                                         .toString().getBytes()), urlEntry.restURL,
116                                         urlEntry.authorization, urlEntry.clientAuth,
117                                         urlEntry.environment);
118                         netLogger.info("[IN|{}|{}|]{}{}", "GUARD", urlEntry.restURL, System.lineSeparator(), response);
119                 } catch (Exception e) {
120                         logger.error("Error in sending RESTful request: ", e);
121                 }
122
123                 return response;
124         }
125
126         /**
127          * This makes an HTTP POST call to a running PDP RESTful servlet to get a
128          * decision.
129          * 
130          * @param file
131          * @return response from guard which contains "Permit" or "Deny"
132          */
133         private String callRESTfulPDP(InputStream is, URL restURL,
134                         String authorization, String clientauth, String environment) {
135                 String response = null;
136                 String rawDecision = null;
137                 HttpURLConnection connection = null;
138                 try {
139
140                         //
141                         // Open up the connection
142                         //
143                         connection = (HttpURLConnection) restURL.openConnection();
144                         connection.setRequestProperty("Content-Type", "application/json");
145                         //
146                         // Setup our method and headers
147                         //
148                         connection.setRequestProperty("Accept", "application/json");
149                         if (authorization != null) {
150                                 connection.setRequestProperty("Authorization", authorization);
151                         }
152                         if (clientauth != null) {
153                                 connection.setRequestProperty("ClientAuth", clientauth);
154                         }
155                         if (environment != null) {
156                                 connection.setRequestProperty("Environment", environment);
157                         }
158                         connection.setConnectTimeout(timeout);
159                         connection.setReadTimeout(timeout);
160                         connection.setRequestMethod("POST");
161                         connection.setUseCaches(false);
162                         //
163                         // Adding this in. It seems the HttpUrlConnection class does NOT
164                         // properly forward our headers for POST re-direction. It does so
165                         // for a GET re-direction.
166                         //
167                         // So we need to handle this ourselves.
168                         //
169                         connection.setInstanceFollowRedirects(false);
170                         connection.setDoOutput(true);
171                         connection.setDoInput(true);
172                         //
173                         // Send the request
174                         //
175                         try (OutputStream os = connection.getOutputStream()) {
176                                 IOUtils.copy(is, os);
177                         }
178                         //
179                         // Do the connect
180                         //
181                         connection.connect();
182                         if (connection.getResponseCode() == 200) {
183                                 //
184                                 // Read the response
185                                 //
186                                 ContentType contentType = null;
187                                 try {
188                                         contentType = ContentType
189                                                         .parse(connection.getContentType());
190
191                                         if (contentType.getMimeType().equalsIgnoreCase(
192                                                         ContentType.APPLICATION_JSON.getMimeType())) {
193                                                 InputStream iStream = connection.getInputStream();
194                                                 int contentLength = connection.getContentLength();
195
196                                                 // if content length is -1, respose is chunked, and
197                                                 // TCP connection will be dropped at the end
198                                                 byte[] buf = new byte[contentLength < 0 ? 1024
199                                                                 : contentLength];
200                                                 int offset = 0;
201                                                 for (;;) {
202                                                         if (offset == contentLength) {
203                                                                 // all expected bytes have been read
204                                                                 response = new String(buf);
205                                                                 break;
206                                                         }
207                                                         int size = iStream.read(buf, offset, buf.length
208                                                                         - offset);
209                                                         if (size < 0) {
210                                                                 if (contentLength > 0) {
211                                                                         logger.error("partial input stream");
212                                                                 } else {
213                                                                         // chunked response --
214                                                                         // dropped connection is expected
215                                                                         response = new String(buf, 0, offset);
216                                                                 }
217                                                                 break;
218                                                         }
219                                                         offset += size;
220                                                 }
221                                         } else {
222                                                 logger.error("unknown content-type: " + contentType);
223                                         }
224
225                                 } catch (Exception e) {
226                                         String message = "Parsing Content-Type: "
227                                                         + connection.getContentType();
228                                         logger.error(message, e);
229                                 }
230
231                         } else {
232                                 logger.error(connection.getResponseCode() + " "
233                                                 + connection.getResponseMessage());
234                         }
235                 } catch (Exception e) {
236                         logger.error(
237                                         "Exception in 'PolicyGuardXacmlHelper.callRESTfulPDP'", e);
238                 }
239                 
240                 //
241                 // Connection may have failed or not been 200 OK, return Indeterminate
242                 //
243                 if(response == null || response.isEmpty()){
244                         return Util.INDETERMINATE;
245                 }
246                 
247                 rawDecision = new JSONObject(response).getString("decision");
248
249                 return rawDecision;
250         }
251
252         public static PolicyGuardResponse ParseXacmlPdpResponse(
253                         com.att.research.xacml.api.Response xacmlResponse) {
254
255                 if (xacmlResponse == null) {
256
257                         //
258                         // In case the actual XACML response was null, create an empty
259                         // response object with decision "Indeterminate"
260                         //
261                         return new PolicyGuardResponse("Indeterminate", null, "");
262                 }
263
264                 Iterator<Result> it_res = xacmlResponse.getResults().iterator();
265
266                 Result res = it_res.next();
267                 String decision_from_xacml_response = res.getDecision().toString();
268                 Iterator<AttributeCategory> it_attr_cat = res.getAttributes()
269                                 .iterator();
270                 UUID req_id_from_xacml_response = null;
271                 String operation_from_xacml_response = "";
272
273                 while (it_attr_cat.hasNext()) {
274                         Iterator<Attribute> it_attr = it_attr_cat.next().getAttributes()
275                                         .iterator();
276                         while (it_attr.hasNext()) {
277                                 Attribute current_attr = it_attr.next();
278                                 String s = current_attr.getAttributeId().stringValue();
279                                 if ("urn:oasis:names:tc:xacml:1.0:request:request-id".equals(s)) {
280                                         Iterator<AttributeValue<?>> it_values = current_attr
281                                                         .getValues().iterator();
282                                         req_id_from_xacml_response = UUID.fromString(it_values
283                                                         .next().getValue().toString());
284                                 }
285                                 if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id"
286                                                 .equals(s)) {
287                                         Iterator<AttributeValue<?>> it_values = current_attr
288                                                         .getValues().iterator();
289                                         operation_from_xacml_response = it_values.next().getValue()
290                                                         .toString();
291                                 }
292
293                         }
294                 }
295
296                 return new PolicyGuardResponse(decision_from_xacml_response,
297                                 req_id_from_xacml_response, operation_from_xacml_response);
298
299         }
300
301         private void init(Properties properties) {
302                 // used to store error messages
303                 StringBuilder sb = new StringBuilder();
304
305                 // fetch these parameters, if they exist
306                 String timeoutString = properties.getProperty("pdpx.timeout");
307                 String disabledString = properties.getProperty("guard.disabled");
308
309                 if (disabledString != null) {
310                         // decode optional 'guard.disabled' parameter
311                         disabled = new Boolean(disabledString);
312                         if (disabled) {
313                                 // skip everything else
314                                 return;
315                         }
316                 }
317
318                 /*
319                  * Decode 'pdpx.*' parameters
320                  */
321
322                 // first, the default parameters
323                 String defaultUser = properties.getProperty("pdpx.username");
324                 String defaultPassword = properties
325                                 .getProperty("pdpx.password");
326                 String defaultClientUser = properties
327                                 .getProperty("pdpx.client.username");
328                 String defaultClientPassword = properties
329                                 .getProperty("pdpx.client.password");
330                 String defaultEnvironment = properties
331                                 .getProperty("pdpx.environment");
332
333                 // now, see which numeric entries (1-9) exist
334                 ArrayList<URLEntry> entries = new ArrayList<>();
335
336                 for (int index = 0; index < 10; index += 1) {
337                         String urlPrefix = "guard.";
338                         String pdpxPrefix = "pdpx.";
339                         if (index != 0) {
340                                 urlPrefix = urlPrefix + index + ".";
341                         }
342
343                         // see if the associated URL exists
344                         String restURLlist = properties.getProperty(urlPrefix + "url");
345                         if (nullOrEmpty(restURLlist)) {
346                                 // no entry for this index
347                                 continue;
348                         }
349
350                         // support a list of entries separated by semicolons. Each entry
351                         // can be:
352                         // URL
353                         // URL,user
354                         // URL,user,password
355                         for (String restURL : restURLlist.split("\\s*;\\s*")) {
356                                 String[] segments = restURL.split("\\s*,\\s*");
357                                 String user = null;
358                                 String password = null;
359
360                                 if (segments.length >= 2) {
361                                         // user id is provided
362                                         restURL = segments[0];
363                                         user = segments[1];
364                                         if (segments.length >= 3) {
365                                                 // password is also provided
366                                                 password = segments[2];
367                                         }
368                                 }
369
370                                 // URL does exist -- create the entry
371                                 URLEntry urlEntry = new URLEntry();
372                                 try {
373                                         urlEntry.restURL = new URL(restURL);
374                                 } catch (java.net.MalformedURLException e) {
375                                         // if we don't have a URL,
376                                         // don't bother with the rest on this one
377                                         sb.append("'").append(urlPrefix).append("url' '")
378                                                         .append(restURL).append("': ").append(e)
379                                                         .append(",");
380                                         continue;
381                                 }
382
383                                 if (nullOrEmpty(user)) {
384                                         // user id was not provided on '*.url' line --
385                                         // extract it from a separate property
386                                         user = properties.getProperty(pdpxPrefix + "username", defaultUser);
387                                 }
388                                 if (nullOrEmpty(password)) {
389                                         // password was not provided on '*.url' line --
390                                         // extract it from a separate property
391                                         password = properties.getProperty(pdpxPrefix + "password",
392                                                         defaultPassword);
393                                 }
394
395                                 // see if 'user' and 'password' entries both exist
396                                 if (!nullOrEmpty(user) && !nullOrEmpty(password)) {
397                                         urlEntry.authorization = "Basic "
398                                                         + Base64.getEncoder().encodeToString(
399                                                                         (user + ":" + password).getBytes());
400                                 }
401
402                                 // see if 'client.user' and 'client.password' entries both exist
403                                 String clientUser = properties.getProperty(pdpxPrefix
404                                                 + "client.username", defaultClientUser);
405                                 String clientPassword = properties.getProperty(pdpxPrefix
406                                                 + "client.password", defaultClientPassword);
407                                 if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) {
408                                         urlEntry.clientAuth = "Basic "
409                                                         + Base64.getEncoder().encodeToString(
410                                                                         (clientUser + ":" + clientPassword)
411                                                                                         .getBytes());
412                                 }
413
414                                 // see if there is an 'environment' entry
415                                 String environment = properties.getProperty(pdpxPrefix
416                                                 + "environment", defaultEnvironment);
417                                 if (!nullOrEmpty(environment)) {
418                                         urlEntry.environment = environment;
419                                 }
420
421                                 // include this URLEntry in the list
422                                 entries.add(urlEntry);
423                         }
424                 }
425
426                 if (entries.size() == 0) {
427                         sb.append("'pdpx.*' -- no URLs specified, ");
428                 } else {
429                         restUrls = entries.toArray(new URLEntry[0]);
430                 }
431
432                 if (timeoutString != null) {
433                         try {
434                                 // decode optional 'pdpx.timeout' parameter
435                                 timeout = Integer.valueOf(timeoutString);
436                         } catch (NumberFormatException e) {
437                                 sb.append("'pdpx.timeout': " + e + ", ");
438                                 logger.trace(e.getLocalizedMessage());
439                         }
440                 }
441
442
443                 // if there are any errors, update 'errorMessage' & disable guard
444                 // queries
445                 if (sb.length() != 0) {
446                         // remove the terminating ", ", and extract resulting error message
447                         sb.setLength(sb.length() - 2);
448                         errorMessage = sb.toString();
449                         disabled = true;
450                         logger.error("Initialization failure: " + errorMessage);
451                 }
452         }
453
454         /**
455          * Check if a string is null or an empty string
456          *
457          * @param value
458          *            the string to be tested
459          * @return 'true' if the string is 'null' or has a length of 0, 'false'
460          *         otherwise
461          */
462         static private boolean nullOrEmpty(String value) {
463                 return (value == null || value.isEmpty());
464         }
465
466 }