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.drools.system.PolicyEngine;
 
  55 import org.onap.policy.guard.PolicyGuardResponse;
 
  56 import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes;
 
  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);
 
  65     private String propfile;
 
  66     private UrlEntry[] restUrls = null;
 
  67     private int restUrlIndex = 0;
 
  69     // REST timeout, initialized from 'pdpx.timeout' property
 
  70     private int timeout = 20000;
 
  72     public PolicyGuardXacmlHelperEmbedded() {
 
  73         init(PolicyEngine.manager.getEnvironment());
 
  76     // initialized from 'pdpx.url' property --
 
  77     // Each entry in 'restUrls' contains a destination URL, and an optional
 
  78     // 'Authorization' header entry. 'restUrlIndex' indicates the next
 
  79     // entry to try -- after each failure, the index is advanced to the
 
  80     // next entry (wrapping to the beginning, if needed).
 
  81     private static class UrlEntry implements Serializable {
 
  82         private static final long serialVersionUID = -8859237552195400518L;
 
  85         String authorization = null;
 
  86         String clientAuth = null;
 
  87         String environment = null;
 
  93      * @param xacmlReq the XACML request
 
  94      * @return the response
 
  96     public String callPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
 
 100         String response = null;
 
 102         if ( propfile != null ) {
 
 103             logger.debug("callEmbeddedPdp");
 
 104             return callEmbeddedPdp(xacmlReq);
 
 107         // Build the json request
 
 109         JSONObject attributes = new JSONObject();
 
 110         attributes.put("actor", xacmlReq.getActorId());
 
 111         attributes.put("recipe", xacmlReq.getOperationId());
 
 112         attributes.put("target", xacmlReq.getTargetId());
 
 113         if (xacmlReq.getClnameId() != null) {
 
 114             attributes.put("clname", xacmlReq.getClnameId());
 
 116         if (xacmlReq.getVfCount() != null) {
 
 117             attributes.put("vfCount", xacmlReq.getVfCount());
 
 119         JSONObject jsonReq = new JSONObject();
 
 120         jsonReq.put("decisionAttributes", attributes);
 
 121         jsonReq.put("onapName", "PDPD");
 
 128             UrlEntry urlEntry = restUrls[restUrlIndex];
 
 129             String jsonRequestString = jsonReq.toString();
 
 130             NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, urlEntry.restUrl.toString(), jsonRequestString);
 
 131             response = callRestfulPdp(new ByteArrayInputStream(jsonReq.toString().getBytes()), urlEntry.restUrl,
 
 132                     urlEntry.authorization, urlEntry.clientAuth, urlEntry.environment);
 
 133             NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, urlEntry.restUrl.toString(), response);
 
 134         } catch (Exception e) {
 
 135             logger.error("Error in sending RESTful request", e);
 
 142      * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision.
 
 144      * @param is the InputStream
 
 145      * @param authorization the Authorization
 
 146      * @param clientauth the ClientAuth
 
 147      * @param environment the Environment
 
 148      * @return response from guard which contains "Permit" or "Deny"
 
 150     private String callRestfulPdp(InputStream is, URL restUrl, String authorization, String clientauth,
 
 151             String environment) {
 
 152         HttpURLConnection connection = null;
 
 156             // Open up the connection
 
 158             connection = (HttpURLConnection) restUrl.openConnection();
 
 159             connection.setRequestProperty("Content-Type", "application/json");
 
 161             // Setup our method and headers
 
 163             connection.setRequestProperty("Accept", "application/json");
 
 164             if (authorization != null) {
 
 165                 connection.setRequestProperty("Authorization", authorization);
 
 167             if (clientauth != null) {
 
 168                 connection.setRequestProperty("ClientAuth", clientauth);
 
 170             if (environment != null) {
 
 171                 connection.setRequestProperty("Environment", environment);
 
 173             connection.setConnectTimeout(timeout);
 
 174             connection.setReadTimeout(timeout);
 
 175             connection.setRequestMethod("POST");
 
 176             connection.setUseCaches(false);
 
 178             // Adding this in. It seems the HttpUrlConnection class does NOT
 
 179             // properly forward our headers for POST re-direction. It does so
 
 180             // for a GET re-direction.
 
 182             // So we need to handle this ourselves.
 
 184             connection.setInstanceFollowRedirects(false);
 
 185             connection.setDoOutput(true);
 
 186             connection.setDoInput(true);
 
 190             try (OutputStream os = connection.getOutputStream()) {
 
 191                 IOUtils.copy(is, os);
 
 197             connection.connect();
 
 199             if (connection.getResponseCode() != 200) {
 
 200                 logger.error("{} {}", connection.getResponseCode(), connection.getResponseMessage());
 
 201                 return Util.INDETERMINATE;
 
 203         } catch (Exception e) {
 
 204             logger.error("Exception in 'PolicyGuardEmbeddedHelper.callRESTfulPDP'", e);
 
 205             return Util.INDETERMINATE;
 
 212             ContentType contentType = ContentType.parse(connection.getContentType());
 
 214             if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) {
 
 215                 InputStream inputStream = connection.getInputStream();
 
 216                 int contentLength = connection.getContentLength();
 
 218                 return readResponseFromStream(inputStream, contentLength);
 
 220                 logger.error("unknown content-type: {}", contentType);
 
 221                 return Util.INDETERMINATE;
 
 224         } catch (Exception e) {
 
 225             String message = "Parsing Content-Type: " + connection.getContentType();
 
 226             logger.error(message, e);
 
 227             return Util.INDETERMINATE;
 
 234      * @param xacmlReq the XACML request
 
 235      * @return the response
 
 237     public String callEmbeddedPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
 
 238         com.att.research.xacml.api.Response response = null;
 
 239         Properties props = new Properties();
 
 243         try ( InputStream is = new FileInputStream(propfile);
 
 244               InputStreamReader isr = new InputStreamReader(is);
 
 245               BufferedReader br = new BufferedReader(isr) ) {
 
 247         } catch (Exception e) {
 
 248             logger.error("Unable to load properties file {}", propfile, e);
 
 251         // Create embedded PDPEngine
 
 253         PDPEngine xacmlPdpEngine;
 
 255             xacmlPdpEngine = ATTPDPEngineFactory.newInstance().newEngine(props);
 
 256         } catch (Exception e) {
 
 257             logger.error("callEmbeddedPdpEngine failed to create new PDPEngine", e);
 
 260         logger.debug("embedded Engine created");
 
 262         // Embedded call to PDP
 
 264         long timeStart = System.currentTimeMillis();
 
 265         if (xacmlReq.getVfCount() == null ) {
 
 266             xacmlReq.setVfCount(1);
 
 269             response = xacmlPdpEngine.decide(RequestParser.parseRequest(xacmlReq));
 
 270         } catch (Exception e) {
 
 271             logger.error("callEmbeddedPdpEngine failed on decide", e);
 
 273         long timeEnd = System.currentTimeMillis();
 
 274         logger.debug("Elapsed Time: {} ms", (timeEnd - timeStart));
 
 276         // Convert response to string
 
 278         logger.debug("converting response to string");
 
 279         PolicyGuardResponse pgr = parseXacmlPdpResponse(response);
 
 280         logger.debug("parsed XacmlPdpResponse {}", pgr);
 
 281         String decision = pgr.getResult();
 
 282         logger.debug("decision={}",decision);
 
 287      * Parse XACML PDP response.
 
 289      * @param xacmlResponse the XACML response
 
 290      * @return the PolicyGuardResponse
 
 292     public static PolicyGuardResponse parseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse) {
 
 293         if (xacmlResponse == null) {
 
 295             // In case the actual XACML response was null, create an empty
 
 296             // response object with decision "Indeterminate"
 
 298             return new PolicyGuardResponse("Indeterminate", null, "");
 
 301         Iterator<Result> itRes = xacmlResponse.getResults().iterator();
 
 303         Result res = itRes.next();
 
 304         String decisionFromXacmlResponse = res.getDecision().toString();
 
 305         Iterator<AttributeCategory> itAttrCat = res.getAttributes().iterator();
 
 306         UUID reqIdFromXacmlResponse = null;
 
 307         String operationFromXacmlResponse = "";
 
 309         while (itAttrCat.hasNext()) {
 
 310             Iterator<Attribute> itAttr = itAttrCat.next().getAttributes().iterator();
 
 311             while (itAttr.hasNext()) {
 
 312                 Attribute currentAttr = itAttr.next();
 
 313                 String attributeId = currentAttr.getAttributeId().stringValue();
 
 314                 if ("urn:oasis:names:tc:xacml:1.0:request:request-id".equals(attributeId)) {
 
 315                     Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
 
 316                     reqIdFromXacmlResponse = UUID.fromString(itValues.next().getValue().toString());
 
 318                 if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id".equals(attributeId)) {
 
 319                     Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
 
 320                     operationFromXacmlResponse = itValues.next().getValue().toString();
 
 325         return new PolicyGuardResponse(decisionFromXacmlResponse, reqIdFromXacmlResponse, operationFromXacmlResponse);
 
 329     private void init(Properties properties) {
 
 330         propfile = properties.getProperty("prop.guard.propfile");
 
 332         // used to store error messages
 
 333         StringBuilder sb = new StringBuilder();
 
 335         // fetch these parameters, if they exist
 
 336         String timeoutString = properties.getProperty("pdpx.timeout");
 
 337         String disabledString = properties.getProperty("guard.disabled");
 
 339         if (disabledString != null && Boolean.parseBoolean(disabledString)) {
 
 343         ArrayList<UrlEntry> entries = initEntries(properties, sb);
 
 345         if (entries.isEmpty()) {
 
 346             sb.append("'pdpx.*' -- no URLs specified, ");
 
 348             restUrls = entries.toArray(new UrlEntry[0]);
 
 351         if (timeoutString != null) {
 
 353                 // decode optional 'pdpx.timeout' parameter
 
 354                 timeout = Integer.valueOf(timeoutString);
 
 355             } catch (NumberFormatException e) {
 
 356                 sb.append("'pdpx.timeout': " + e + ", ");
 
 357                 logger.trace(e.getLocalizedMessage());
 
 362         // if there are any errors, update 'errorMessage' & disable guard
 
 364         if (sb.length() != 0) {
 
 365             // remove the terminating ", ", and extract resulting error message
 
 366             sb.setLength(sb.length() - 2);
 
 367             String errorMessage = sb.toString();
 
 368             logger.error("Initialization failure: {}", errorMessage);
 
 372     private ArrayList<UrlEntry> initEntries(Properties properties, StringBuilder sb) {
 
 373         // now, see which numeric entries (1-9) exist
 
 374         ArrayList<UrlEntry> entries = new ArrayList<>();
 
 376         for (int index = 0; index < 10; index += 1) {
 
 377             String urlPrefix = "guard.";
 
 379                 urlPrefix = urlPrefix + index + ".";
 
 382             // see if the associated URL exists
 
 383             String restUrllist = properties.getProperty(urlPrefix + "url");
 
 384             if (nullOrEmpty(restUrllist)) {
 
 385                 // no entry for this index
 
 389             // support a list of entries separated by semicolons. Each entry
 
 394             for (String restUrl : restUrllist.split("\\s*;\\s*")) {
 
 395                 UrlEntry entry = initRestUrl(properties, sb, restUrl);
 
 396                 // include this URLEntry in the list
 
 406     private UrlEntry initRestUrl(Properties properties, StringBuilder sb, String restUrl) {
 
 407         String urlPrefix = "guard.";
 
 408         String pdpxPrefix = "pdpx.";
 
 410         String[] segments = restUrl.split("\\s*,\\s*");
 
 412         String password = null;
 
 414         if (segments.length >= 2) {
 
 415             // user id is provided
 
 416             restUrl = segments[0];
 
 418             if (segments.length >= 3) {
 
 419                 // password is also provided
 
 420                 password = segments[2];
 
 424         // URL does exist -- create the entry
 
 425         UrlEntry urlEntry = new UrlEntry();
 
 427             urlEntry.restUrl = new URL(restUrl);
 
 428         } catch (java.net.MalformedURLException e) {
 
 429             // if we don't have a URL,
 
 430             // don't bother with the rest on this one
 
 431             sb.append("'").append(urlPrefix).append("url' '").append(restUrl).append("': ").append(e).append(",");
 
 435         if (nullOrEmpty(user)) {
 
 436             // user id was not provided on '*.url' line --
 
 437             // extract it from a separate property
 
 438             user = properties.getProperty(pdpxPrefix + "username", properties.getProperty("pdpx.username"));
 
 440         if (nullOrEmpty(password)) {
 
 441             // password was not provided on '*.url' line --
 
 442             // extract it from a separate property
 
 443             password = properties.getProperty(pdpxPrefix + "password", properties.getProperty("pdpx.password"));
 
 446         // see if 'user' and 'password' entries both exist
 
 447         if (!nullOrEmpty(user) && !nullOrEmpty(password)) {
 
 448             urlEntry.authorization = "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes());
 
 451         // see if 'client.user' and 'client.password' entries both exist
 
 453                 properties.getProperty(pdpxPrefix + "client.username", properties.getProperty("pdpx.client.username"));
 
 454         String clientPassword =
 
 455                 properties.getProperty(pdpxPrefix + "client.password", properties.getProperty("pdpx.client.password"));
 
 456         if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) {
 
 457             urlEntry.clientAuth =
 
 458                     "Basic " + Base64.getEncoder().encodeToString((clientUser + ":" + clientPassword).getBytes());
 
 461         // see if there is an 'environment' entry
 
 463                 properties.getProperty(pdpxPrefix + "environment", properties.getProperty("pdpx.environment"));
 
 464         if (!nullOrEmpty(environment)) {
 
 465             urlEntry.environment = environment;
 
 472      * Check if a string is null or an empty string.
 
 474      * @param value the string to be tested
 
 475      * @return 'true' if the string is 'null' or has a length of 0, 'false' otherwise
 
 477     private static boolean nullOrEmpty(String value) {
 
 478         return (value == null || value.isEmpty());
 
 481     private static String readResponseFromStream(InputStream inputStream, int contentLength) throws IOException {
 
 482         // if content length is -1, response is chunked, and
 
 483         // TCP connection will be dropped at the end
 
 484         byte[] buf = new byte[contentLength < 0 ? 1024 : contentLength];
 
 488             int size = inputStream.read(buf, offset, buf.length - offset);
 
 490                 // In a chunked response a dropped connection is expected, but not if the response
 
 492                 if (contentLength > 0) {
 
 493                     logger.error("partial input stream");
 
 499         while (offset != contentLength);
 
 501         String response = new String(buf, 0, offset);
 
 504         // Connection may have failed or not been 200 OK, return Indeterminate
 
 506         if (response.isEmpty()) {
 
 507             return Util.INDETERMINATE;
 
 510         return new JSONObject(response).getString("decision");