2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.policy.guard;
24 import com.att.research.xacml.api.Attribute;
25 import com.att.research.xacml.api.AttributeCategory;
26 import com.att.research.xacml.api.AttributeValue;
27 import com.att.research.xacml.api.Result;
28 import com.att.research.xacml.api.pdp.PDPEngine;
29 import com.att.research.xacml.std.annotations.RequestParser;
30 import com.att.research.xacmlatt.pdp.ATTPDPEngineFactory;
32 import java.io.BufferedReader;
33 import java.io.ByteArrayInputStream;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.io.OutputStream;
39 import java.io.Serializable;
40 import java.net.HttpURLConnection;
42 import java.util.ArrayList;
43 import java.util.Base64;
44 import java.util.Iterator;
45 import java.util.Properties;
46 import java.util.UUID;
48 import org.apache.commons.io.IOUtils;
49 import org.apache.http.entity.ContentType;
50 import org.json.JSONObject;
51 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
52 import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
53 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
54 import org.onap.policy.database.operationshistory.Dbao;
55 import org.onap.policy.drools.system.PolicyEngine;
56 import org.onap.policy.guard.PolicyGuardResponse;
57 import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes;
58 import org.onap.policy.guard.Util;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
63 public class PolicyGuardXacmlHelperEmbedded {
64 private static final Logger logger = LoggerFactory.getLogger(PolicyGuardXacmlHelperEmbedded.class);
66 private String propfile;
67 private UrlEntry[] restUrls = null;
68 private int restUrlIndex = 0;
70 // REST timeout, initialized from 'pdpx.timeout' property
71 private int timeout = 20000;
73 public PolicyGuardXacmlHelperEmbedded() {
74 init(PolicyEngine.manager.getEnvironment());
77 // initialized from 'pdpx.url' property --
78 // Each entry in 'restUrls' contains a destination URL, and an optional
79 // 'Authorization' header entry. 'restUrlIndex' indicates the next
80 // entry to try -- after each failure, the index is advanced to the
81 // next entry (wrapping to the beginning, if needed).
82 private static class UrlEntry implements Serializable {
83 private static final long serialVersionUID = -8859237552195400518L;
86 String authorization = null;
87 String clientAuth = null;
88 String environment = null;
94 * @param xacmlReq the XACML request
95 * @return the response
97 public String callPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
101 String response = null;
103 if ( propfile != null ) {
104 logger.debug("callEmbeddedPdp");
105 return callEmbeddedPdp(xacmlReq);
108 // Build the json request
110 JSONObject attributes = new JSONObject();
111 attributes.put("actor", xacmlReq.getActorId());
112 attributes.put("recipe", xacmlReq.getOperationId());
113 attributes.put("target", xacmlReq.getTargetId());
114 if (xacmlReq.getClnameId() != null) {
115 attributes.put("clname", xacmlReq.getClnameId());
117 if (xacmlReq.getVfCount() != null) {
118 attributes.put("vfCount", xacmlReq.getVfCount());
120 JSONObject jsonReq = new JSONObject();
121 jsonReq.put("decisionAttributes", attributes);
122 jsonReq.put("onapName", "PDPD");
129 UrlEntry urlEntry = restUrls[restUrlIndex];
130 String jsonRequestString = jsonReq.toString();
131 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, urlEntry.restUrl.toString(), jsonRequestString);
132 response = callRestfulPdp(new ByteArrayInputStream(jsonReq.toString().getBytes()), urlEntry.restUrl,
133 urlEntry.authorization, urlEntry.clientAuth, urlEntry.environment);
134 NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, urlEntry.restUrl.toString(), response);
135 } catch (Exception e) {
136 logger.error("Error in sending RESTful request", e);
143 * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision.
145 * @param is the InputStream
146 * @param authorization the Authorization
147 * @param clientauth the ClientAuth
148 * @param environment the Environment
149 * @return response from guard which contains "Permit" or "Deny"
151 private String callRestfulPdp(InputStream is, URL restUrl, String authorization, String clientauth,
152 String environment) {
153 HttpURLConnection connection = null;
157 // Open up the connection
159 connection = (HttpURLConnection) restUrl.openConnection();
160 connection.setRequestProperty("Content-Type", "application/json");
162 // Setup our method and headers
164 connection.setRequestProperty("Accept", "application/json");
165 if (authorization != null) {
166 connection.setRequestProperty("Authorization", authorization);
168 if (clientauth != null) {
169 connection.setRequestProperty("ClientAuth", clientauth);
171 if (environment != null) {
172 connection.setRequestProperty("Environment", environment);
174 connection.setConnectTimeout(timeout);
175 connection.setReadTimeout(timeout);
176 connection.setRequestMethod("POST");
177 connection.setUseCaches(false);
179 // Adding this in. It seems the HttpUrlConnection class does NOT
180 // properly forward our headers for POST re-direction. It does so
181 // for a GET re-direction.
183 // So we need to handle this ourselves.
185 connection.setInstanceFollowRedirects(false);
186 connection.setDoOutput(true);
187 connection.setDoInput(true);
191 try (OutputStream os = connection.getOutputStream()) {
192 IOUtils.copy(is, os);
198 connection.connect();
200 if (connection.getResponseCode() != 200) {
201 logger.error("{} {}", connection.getResponseCode(), connection.getResponseMessage());
202 return Util.INDETERMINATE;
204 } catch (Exception e) {
205 logger.error("Exception in 'PolicyGuardEmbeddedHelper.callRESTfulPDP'", e);
206 return Util.INDETERMINATE;
213 ContentType contentType = ContentType.parse(connection.getContentType());
215 if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) {
216 InputStream inputStream = connection.getInputStream();
217 int contentLength = connection.getContentLength();
219 return readResponseFromStream(inputStream, contentLength);
221 logger.error("unknown content-type: {}", contentType);
222 return Util.INDETERMINATE;
225 } catch (Exception e) {
226 String message = "Parsing Content-Type: " + connection.getContentType();
227 logger.error(message, e);
228 return Util.INDETERMINATE;
235 * @param xacmlReq the XACML request
236 * @return the response
238 public String callEmbeddedPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
239 com.att.research.xacml.api.Response response = null;
240 Properties props = new Properties();
244 try ( InputStream is = new FileInputStream(propfile);
245 InputStreamReader isr = new InputStreamReader(is);
246 BufferedReader br = new BufferedReader(isr) ) {
248 } catch (Exception e) {
249 logger.error("Unable to load properties file {}", propfile, e);
252 // Create embedded PDPEngine
254 PDPEngine xacmlPdpEngine;
256 xacmlPdpEngine = ATTPDPEngineFactory.newInstance().newEngine(props);
257 } catch (Exception e) {
258 logger.error("callEmbeddedPdpEngine failed to create new PDPEngine", e);
261 logger.debug("embedded Engine created");
263 // Embedded call to PDP
265 long timeStart = System.currentTimeMillis();
266 if (xacmlReq.getVfCount() == null ) {
267 xacmlReq.setVfCount(1);
270 response = xacmlPdpEngine.decide(RequestParser.parseRequest(xacmlReq));
271 } catch (Exception e) {
272 logger.error("callEmbeddedPdpEngine failed on decide", e);
274 long timeEnd = System.currentTimeMillis();
275 logger.debug("Elapsed Time: {} ms", (timeEnd - timeStart));
277 // Convert response to string
279 logger.debug("converting response to string");
280 PolicyGuardResponse pgr = parseXacmlPdpResponse(response);
281 logger.debug("parsed XacmlPdpResponse {}", pgr);
282 String decision = pgr.getResult();
283 logger.debug("decision={}",decision);
288 * Parse XACML PDP response.
290 * @param xacmlResponse the XACML response
291 * @return the PolicyGuardResponse
293 public static PolicyGuardResponse parseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse) {
294 if (xacmlResponse == null) {
296 // In case the actual XACML response was null, create an empty
297 // response object with decision "Indeterminate"
299 return new PolicyGuardResponse("Indeterminate", null, "");
302 Iterator<Result> itRes = xacmlResponse.getResults().iterator();
304 Result res = itRes.next();
305 String decisionFromXacmlResponse = res.getDecision().toString();
306 Iterator<AttributeCategory> itAttrCat = res.getAttributes().iterator();
307 UUID reqIdFromXacmlResponse = null;
308 String operationFromXacmlResponse = "";
310 while (itAttrCat.hasNext()) {
311 Iterator<Attribute> itAttr = itAttrCat.next().getAttributes().iterator();
312 while (itAttr.hasNext()) {
313 Attribute currentAttr = itAttr.next();
314 String attributeId = currentAttr.getAttributeId().stringValue();
315 if ("urn:org:onap:guard:request:request-id".equals(attributeId)) {
316 Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
317 reqIdFromXacmlResponse = UUID.fromString(itValues.next().getValue().toString());
319 if ("urn:org:onap:guard:operation:operation-id".equals(attributeId)) {
320 Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
321 operationFromXacmlResponse = itValues.next().getValue().toString();
326 return new PolicyGuardResponse(decisionFromXacmlResponse, reqIdFromXacmlResponse, operationFromXacmlResponse);
330 private void init(Properties properties) {
331 propfile = properties.getProperty("prop.guard.propfile");
333 // used to store error messages
334 StringBuilder sb = new StringBuilder();
336 // fetch these parameters, if they exist
337 String timeoutString = properties.getProperty("pdpx.timeout");
338 String disabledString = properties.getProperty("guard.disabled");
340 if (disabledString != null && Boolean.parseBoolean(disabledString)) {
344 ArrayList<UrlEntry> entries = initEntries(properties, sb);
346 if (entries.isEmpty()) {
347 sb.append("'pdpx.*' -- no URLs specified, ");
349 restUrls = entries.toArray(new UrlEntry[0]);
352 if (timeoutString != null) {
354 // decode optional 'pdpx.timeout' parameter
355 timeout = Integer.valueOf(timeoutString);
356 } catch (NumberFormatException e) {
357 sb.append("'pdpx.timeout': " + e + ", ");
358 logger.trace(e.getLocalizedMessage());
363 // if there are any errors, update 'errorMessage' & disable guard
365 if (sb.length() != 0) {
366 // remove the terminating ", ", and extract resulting error message
367 sb.setLength(sb.length() - 2);
368 String errorMessage = sb.toString();
369 logger.error("Initialization failure: {}", errorMessage);
373 private ArrayList<UrlEntry> initEntries(Properties properties, StringBuilder sb) {
374 // now, see which numeric entries (1-9) exist
375 ArrayList<UrlEntry> entries = new ArrayList<>();
377 for (int index = 0; index < 10; index += 1) {
378 String urlPrefix = "guard.";
380 urlPrefix = urlPrefix + index + ".";
383 // see if the associated URL exists
384 String restUrllist = properties.getProperty(urlPrefix + "url");
385 if (nullOrEmpty(restUrllist)) {
386 // no entry for this index
390 // support a list of entries separated by semicolons. Each entry
395 for (String restUrl : restUrllist.split("\\s*;\\s*")) {
396 UrlEntry entry = initRestUrl(properties, sb, restUrl);
397 // include this URLEntry in the list
407 private UrlEntry initRestUrl(Properties properties, StringBuilder sb, String restUrl) {
408 String urlPrefix = "guard.";
409 String pdpxPrefix = "pdpx.";
411 String[] segments = restUrl.split("\\s*,\\s*");
413 String password = null;
415 if (segments.length >= 2) {
416 // user id is provided
417 restUrl = segments[0];
419 if (segments.length >= 3) {
420 // password is also provided
421 password = segments[2];
425 // URL does exist -- create the entry
426 UrlEntry urlEntry = new UrlEntry();
428 urlEntry.restUrl = new URL(restUrl);
429 } catch (java.net.MalformedURLException e) {
430 // if we don't have a URL,
431 // don't bother with the rest on this one
432 sb.append("'").append(urlPrefix).append("url' '").append(restUrl).append("': ").append(e).append(",");
436 if (nullOrEmpty(user)) {
437 // user id was not provided on '*.url' line --
438 // extract it from a separate property
439 user = properties.getProperty(pdpxPrefix + "username", properties.getProperty("pdpx.username"));
441 if (nullOrEmpty(password)) {
442 // password was not provided on '*.url' line --
443 // extract it from a separate property
444 password = properties.getProperty(pdpxPrefix + "password", properties.getProperty("pdpx.password"));
447 // see if 'user' and 'password' entries both exist
448 if (!nullOrEmpty(user) && !nullOrEmpty(password)) {
449 urlEntry.authorization = "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes());
452 // see if 'client.user' and 'client.password' entries both exist
454 properties.getProperty(pdpxPrefix + "client.username", properties.getProperty("pdpx.client.username"));
455 String clientPassword =
456 properties.getProperty(pdpxPrefix + "client.password", properties.getProperty("pdpx.client.password"));
457 if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) {
458 urlEntry.clientAuth =
459 "Basic " + Base64.getEncoder().encodeToString((clientUser + ":" + clientPassword).getBytes());
462 // see if there is an 'environment' entry
464 properties.getProperty(pdpxPrefix + "environment", properties.getProperty("pdpx.environment"));
465 if (!nullOrEmpty(environment)) {
466 urlEntry.environment = environment;
473 * Check if a string is null or an empty string.
475 * @param value the string to be tested
476 * @return 'true' if the string is 'null' or has a length of 0, 'false' otherwise
478 private static boolean nullOrEmpty(String value) {
479 return (value == null || value.isEmpty());
482 private static String readResponseFromStream(InputStream inputStream, int contentLength) throws IOException {
483 // if content length is -1, response is chunked, and
484 // TCP connection will be dropped at the end
485 byte[] buf = new byte[contentLength < 0 ? 1024 : contentLength];
489 int size = inputStream.read(buf, offset, buf.length - offset);
491 // In a chunked response a dropped connection is expected, but not if the response
493 if (contentLength > 0) {
494 logger.error("partial input stream");
500 while (offset != contentLength);
502 String response = new String(buf, 0, offset);
505 // Connection may have failed or not been 200 OK, return Indeterminate
507 if (response.isEmpty()) {
508 return Util.INDETERMINATE;
511 return new JSONObject(response).getString("decision");