2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.policy.guard;
23 import com.att.research.xacml.api.Attribute;
24 import com.att.research.xacml.api.AttributeCategory;
25 import com.att.research.xacml.api.AttributeValue;
26 import com.att.research.xacml.api.Result;
27 import com.att.research.xacml.api.pdp.PDPEngine;
28 import com.att.research.xacml.api.pdp.PDPException;
29 import com.att.research.xacml.api.pdp.PDPEngineFactory;
30 import com.att.research.xacmlatt.pdp.ATTPDPEngineFactory;
31 import com.att.research.xacml.std.annotations.RequestParser;
33 import com.google.gson.Gson;
35 import java.io.BufferedReader;
36 import java.io.ByteArrayInputStream;
37 import java.io.FileInputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.io.OutputStream;
42 import java.io.Serializable;
43 import java.net.HttpURLConnection;
45 import java.util.ArrayList;
46 import java.util.Base64;
47 import java.util.Iterator;
48 import java.util.Properties;
49 import java.util.UUID;
51 import org.apache.commons.io.IOUtils;
52 import org.apache.http.entity.ContentType;
53 import org.json.JSONObject;
54 import org.onap.policy.drools.system.PolicyEngine;
55 import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes;
56 import org.onap.policy.guard.PolicyGuardResponse;
57 import org.onap.policy.guard.Util;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 public class PolicyGuardXacmlHelperEmbedded {
63 private static final Logger logger = LoggerFactory.getLogger(PolicyGuardXacmlHelperEmbedded.class);
64 private static final Logger netLogger =
65 LoggerFactory.getLogger(org.onap.policy.common.endpoints.event.comm.Topic.NETWORK_LOGGER);
67 // Constant for the systme line separator
68 private static final String SYSTEM_LS = System.lineSeparator();
69 private static String propfile;
71 public PolicyGuardXacmlHelperEmbedded() {
72 init(PolicyEngine.manager.getEnvironment());
75 // initialized from 'pdpx.url' property --
76 // Each entry in 'restUrls' contains a destination URL, and an optional
77 // 'Authorization' header entry. 'restUrlIndex' indicates the next
78 // entry to try -- after each failure, the index is advanced to the
79 // next entry (wrapping to the beginning, if needed).
80 private static class UrlEntry implements Serializable {
81 private static final long serialVersionUID = -8859237552195400518L;
84 String authorization = null;
85 String clientAuth = null;
86 String environment = null;
89 private UrlEntry[] restUrls = null;
90 private int restUrlIndex = 0;
92 // REST timeout, initialized from 'pdpx.timeout' property
93 private int timeout = 20000;
98 * @param xacmlReq the XACML request
99 * @return the response
101 public String callPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
103 // Send it to the PDP
105 String response = null;
107 if ( propfile != null ) {
108 logger.debug("callEmbeddedPdp");
109 return callEmbeddedPdp(xacmlReq);
112 // Build the json request
114 JSONObject attributes = new JSONObject();
115 attributes.put("actor", xacmlReq.getActorID());
116 attributes.put("recipe", xacmlReq.getOperationID());
117 attributes.put("target", xacmlReq.getTargetID());
118 if (xacmlReq.getClnameID() != null) {
119 attributes.put("clname", xacmlReq.getClnameID());
121 if (xacmlReq.getVfCount() != null) {
122 attributes.put("vfCount", xacmlReq.getVfCount());
124 JSONObject jsonReq = new JSONObject();
125 jsonReq.put("decisionAttributes", attributes);
126 jsonReq.put("onapName", "PDPD");
133 UrlEntry urlEntry = restUrls[restUrlIndex];
134 String jsonRequestString = jsonReq.toString();
135 netLogger.info("[OUT|{}|{}|]{}{}", "GUARD", urlEntry.restUrl, SYSTEM_LS, jsonRequestString);
136 response = callRestfulPdp(new ByteArrayInputStream(jsonReq.toString().getBytes()), urlEntry.restUrl,
137 urlEntry.authorization, urlEntry.clientAuth, urlEntry.environment);
138 netLogger.info("[IN|{}|{}|]{}{}", "GUARD", urlEntry.restUrl, SYSTEM_LS, response);
139 } catch (Exception e) {
140 logger.error("Error in sending RESTful request: ", e);
147 * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision.
149 * @param is the InputStream
150 * @param authorization the Authorization
151 * @param clientauth the ClientAuth
152 * @param environment the Environment
153 * @return response from guard which contains "Permit" or "Deny"
155 private String callRestfulPdp(InputStream is, URL restURL, String authorization, String clientauth,
156 String environment) {
157 HttpURLConnection connection = null;
161 // Open up the connection
163 connection = (HttpURLConnection) restURL.openConnection();
164 connection.setRequestProperty("Content-Type", "application/json");
166 // Setup our method and headers
168 connection.setRequestProperty("Accept", "application/json");
169 if (authorization != null) {
170 connection.setRequestProperty("Authorization", authorization);
172 if (clientauth != null) {
173 connection.setRequestProperty("ClientAuth", clientauth);
175 if (environment != null) {
176 connection.setRequestProperty("Environment", environment);
178 connection.setConnectTimeout(timeout);
179 connection.setReadTimeout(timeout);
180 connection.setRequestMethod("POST");
181 connection.setUseCaches(false);
183 // Adding this in. It seems the HttpUrlConnection class does NOT
184 // properly forward our headers for POST re-direction. It does so
185 // for a GET re-direction.
187 // So we need to handle this ourselves.
189 connection.setInstanceFollowRedirects(false);
190 connection.setDoOutput(true);
191 connection.setDoInput(true);
195 try (OutputStream os = connection.getOutputStream()) {
196 IOUtils.copy(is, os);
202 connection.connect();
204 if (connection.getResponseCode() != 200) {
205 logger.error(connection.getResponseCode() + " " + connection.getResponseMessage());
206 return Util.INDETERMINATE;
208 } catch (Exception e) {
209 logger.error("Exception in 'PolicyGuardEmbeddedHelper.callRESTfulPDP'", e);
210 return Util.INDETERMINATE;
217 ContentType contentType = ContentType.parse(connection.getContentType());
219 if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) {
220 InputStream inputStream = connection.getInputStream();
221 int contentLength = connection.getContentLength();
223 return readResponseFromStream(inputStream, contentLength);
225 logger.error("unknown content-type: {}", contentType);
226 return Util.INDETERMINATE;
229 } catch (Exception e) {
230 String message = "Parsing Content-Type: " + connection.getContentType();
231 logger.error(message, e);
232 return Util.INDETERMINATE;
239 * @param xacmlReq the XACML request
240 * @return the response
242 public static String callEmbeddedPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
243 com.att.research.xacml.api.Response response = null;
244 Properties props = new Properties();
248 try ( InputStream is = new FileInputStream(propfile);
249 InputStreamReader isr = new InputStreamReader(is);
250 BufferedReader br = new BufferedReader(isr) ) {
252 } catch (Exception e) {
253 logger.error("Unable to load properties file {} {}", propfile, e.getMessage());
256 // Create embedded PDPEngine
258 PDPEngine xacmlPdpEngine;
260 xacmlPdpEngine = ATTPDPEngineFactory.newInstance().newEngine(props);
261 } catch (Exception e) {
262 logger.error("Failed to create new PDPEngine {}", e.getMessage());
265 logger.debug("embedded Engine created");
267 // Embedded call to PDP
269 long lTimeStart = System.currentTimeMillis();
270 if (xacmlReq.getVfCount() == null ) {
271 xacmlReq.setVfCount(1);
274 response = xacmlPdpEngine.decide(RequestParser.parseRequest(xacmlReq));
275 } catch (Exception e) {
276 logger.error(e.getMessage(), e);
278 long lTimeEnd = System.currentTimeMillis();
279 logger.debug("Elapsed Time: {} ms", (lTimeEnd - lTimeStart));
281 // Convert response to string
283 logger.debug("converting response to string");
284 PolicyGuardResponse pgr = parseXacmlPdpResponse(response);
285 logger.debug("parsed XacmlPdpResponse {}", pgr);
286 String decision = pgr.getResult();
287 logger.debug("decision={}",decision);
292 * Parse XACML PDP response.
294 * @param xacmlResponse the XACML response
295 * @return the PolicyGuardResponse
297 public static PolicyGuardResponse parseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse) {
298 if (xacmlResponse == null) {
300 // In case the actual XACML response was null, create an empty
301 // response object with decision "Indeterminate"
303 return new PolicyGuardResponse("Indeterminate", null, "");
306 Iterator<Result> itRes = xacmlResponse.getResults().iterator();
308 Result res = itRes.next();
309 String decisionFromXacmlResponse = res.getDecision().toString();
310 Iterator<AttributeCategory> itAttrCat = res.getAttributes().iterator();
311 UUID reqIdFromXacmlResponse = null;
312 String operationFromXacmlResponse = "";
314 while (itAttrCat.hasNext()) {
315 Iterator<Attribute> itAttr = itAttrCat.next().getAttributes().iterator();
316 while (itAttr.hasNext()) {
317 Attribute currentAttr = itAttr.next();
318 String attributeId = currentAttr.getAttributeId().stringValue();
319 if ("urn:oasis:names:tc:xacml:1.0:request:request-id".equals(attributeId)) {
320 Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
321 reqIdFromXacmlResponse = UUID.fromString(itValues.next().getValue().toString());
323 if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id".equals(attributeId)) {
324 Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
325 operationFromXacmlResponse = itValues.next().getValue().toString();
330 return new PolicyGuardResponse(decisionFromXacmlResponse, reqIdFromXacmlResponse, operationFromXacmlResponse);
334 private void init(Properties properties) {
335 propfile = properties.getProperty("prop.guard.propfile");
337 // used to store error messages
338 StringBuilder sb = new StringBuilder();
340 // fetch these parameters, if they exist
341 String timeoutString = properties.getProperty("pdpx.timeout");
342 String disabledString = properties.getProperty("guard.disabled");
344 if (disabledString != null && Boolean.parseBoolean(disabledString)) {
348 ArrayList<UrlEntry> entries = initEntries(properties, sb);
350 if (entries.isEmpty()) {
351 sb.append("'pdpx.*' -- no URLs specified, ");
353 restUrls = entries.toArray(new UrlEntry[0]);
356 if (timeoutString != null) {
358 // decode optional 'pdpx.timeout' parameter
359 timeout = Integer.valueOf(timeoutString);
360 } catch (NumberFormatException e) {
361 sb.append("'pdpx.timeout': " + e + ", ");
362 logger.trace(e.getLocalizedMessage());
367 // if there are any errors, update 'errorMessage' & disable guard
369 if (sb.length() != 0) {
370 // remove the terminating ", ", and extract resulting error message
371 sb.setLength(sb.length() - 2);
372 String errorMessage = sb.toString();
373 logger.error("Initialization failure: {}", errorMessage);
377 private ArrayList<UrlEntry> initEntries(Properties properties, StringBuilder sb) {
378 // now, see which numeric entries (1-9) exist
379 ArrayList<UrlEntry> entries = new ArrayList<>();
381 for (int index = 0; index < 10; index += 1) {
382 String urlPrefix = "guard.";
384 urlPrefix = urlPrefix + index + ".";
387 // see if the associated URL exists
388 String restUrllist = properties.getProperty(urlPrefix + "url");
389 if (nullOrEmpty(restUrllist)) {
390 // no entry for this index
394 // support a list of entries separated by semicolons. Each entry
399 for (String restUrl : restUrllist.split("\\s*;\\s*")) {
400 UrlEntry entry = initRestUrl(properties, sb, restUrl);
401 // include this URLEntry in the list
411 private UrlEntry initRestUrl(Properties properties, StringBuilder sb, String restUrl) {
412 String urlPrefix = "guard.";
413 String pdpxPrefix = "pdpx.";
415 String[] segments = restUrl.split("\\s*,\\s*");
417 String password = null;
419 if (segments.length >= 2) {
420 // user id is provided
421 restUrl = segments[0];
423 if (segments.length >= 3) {
424 // password is also provided
425 password = segments[2];
429 // URL does exist -- create the entry
430 UrlEntry urlEntry = new UrlEntry();
432 urlEntry.restUrl = new URL(restUrl);
433 } catch (java.net.MalformedURLException e) {
434 // if we don't have a URL,
435 // don't bother with the rest on this one
436 sb.append("'").append(urlPrefix).append("url' '").append(restUrl).append("': ").append(e).append(",");
440 if (nullOrEmpty(user)) {
441 // user id was not provided on '*.url' line --
442 // extract it from a separate property
443 user = properties.getProperty(pdpxPrefix + "username", properties.getProperty("pdpx.username"));
445 if (nullOrEmpty(password)) {
446 // password was not provided on '*.url' line --
447 // extract it from a separate property
448 password = properties.getProperty(pdpxPrefix + "password", properties.getProperty("pdpx.password"));
451 // see if 'user' and 'password' entries both exist
452 if (!nullOrEmpty(user) && !nullOrEmpty(password)) {
453 urlEntry.authorization = "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes());
456 // see if 'client.user' and 'client.password' entries both exist
458 properties.getProperty(pdpxPrefix + "client.username", properties.getProperty("pdpx.client.username"));
459 String clientPassword =
460 properties.getProperty(pdpxPrefix + "client.password", properties.getProperty("pdpx.client.password"));
461 if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) {
462 urlEntry.clientAuth =
463 "Basic " + Base64.getEncoder().encodeToString((clientUser + ":" + clientPassword).getBytes());
466 // see if there is an 'environment' entry
468 properties.getProperty(pdpxPrefix + "environment", properties.getProperty("pdpx.environment"));
469 if (!nullOrEmpty(environment)) {
470 urlEntry.environment = environment;
477 * Check if a string is null or an empty string.
479 * @param value the string to be tested
480 * @return 'true' if the string is 'null' or has a length of 0, 'false' otherwise
482 private static boolean nullOrEmpty(String value) {
483 return (value == null || value.isEmpty());
486 private static String readResponseFromStream(InputStream inputStream, int contentLength) throws IOException {
487 // if content length is -1, response is chunked, and
488 // TCP connection will be dropped at the end
489 byte[] buf = new byte[contentLength < 0 ? 1024 : contentLength];
493 int size = inputStream.read(buf, offset, buf.length - offset);
495 // In a chunked response a dropped connection is expected, but not if the response
497 if (contentLength > 0) {
498 logger.error("partial input stream");
504 while (offset != contentLength);
506 String response = new String(buf, 0, offset);
509 // Connection may have failed or not been 200 OK, return Indeterminate
511 if (response.isEmpty()) {
512 return Util.INDETERMINATE;
515 return new JSONObject(response).getString("decision");