2 * ============LICENSE_START=======================================================
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
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 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;
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;
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;
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;
50 public class PolicyGuardXacmlHelper {
52 private static final Logger logger = LoggerFactory
53 .getLogger(PolicyGuardXacmlHelper.class);
55 private static final Logger netLogger = LoggerFactory.getLogger(org.onap.policy.drools.event.comm.Topic.NETWORK_LOGGER);
57 public PolicyGuardXacmlHelper() {
58 init(PolicyEngine.manager.getEnvironment());
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 {
68 String authorization = null;
69 String clientAuth = null;
70 String environment = null;
73 private URLEntry[] restUrls = null;
74 private int restUrlIndex = 0;
76 // REST timeout, initialized from 'pdpx.timeout' property
77 private int timeout = 20000;
80 // initialized from 'guard.disabled', but may also be set to 'true' if
81 // there is an initialization error
82 private boolean disabled = false;
84 // errors that forced 'disabled' to be set to 'true'
85 private String errorMessage = null;
87 public String callPDP(PolicyGuardXacmlRequestAttributes xacmlReq) {
91 String response = null;
94 // Build the json request
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());
103 JSONObject jsonReq = new JSONObject();
104 jsonReq.put("decisionAttributes", attributes);
105 jsonReq.put("onapName", "PDPD");
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);
127 * This makes an HTTP POST call to a running PDP RESTful servlet to get a
131 * @return response from guard which contains "Permit" or "Deny"
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;
141 // Open up the connection
143 connection = (HttpURLConnection) restURL.openConnection();
144 connection.setRequestProperty("Content-Type", "application/json");
146 // Setup our method and headers
148 connection.setRequestProperty("Accept", "application/json");
149 if (authorization != null) {
150 connection.setRequestProperty("Authorization", authorization);
152 if (clientauth != null) {
153 connection.setRequestProperty("ClientAuth", clientauth);
155 if (environment != null) {
156 connection.setRequestProperty("Environment", environment);
158 connection.setConnectTimeout(timeout);
159 connection.setReadTimeout(timeout);
160 connection.setRequestMethod("POST");
161 connection.setUseCaches(false);
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.
167 // So we need to handle this ourselves.
169 connection.setInstanceFollowRedirects(false);
170 connection.setDoOutput(true);
171 connection.setDoInput(true);
175 try (OutputStream os = connection.getOutputStream()) {
176 IOUtils.copy(is, os);
181 connection.connect();
182 if (connection.getResponseCode() == 200) {
186 ContentType contentType = null;
188 contentType = ContentType
189 .parse(connection.getContentType());
191 if (contentType.getMimeType().equalsIgnoreCase(
192 ContentType.APPLICATION_JSON.getMimeType())) {
193 InputStream iStream = connection.getInputStream();
194 int contentLength = connection.getContentLength();
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
202 if (offset == contentLength) {
203 // all expected bytes have been read
204 response = new String(buf);
207 int size = iStream.read(buf, offset, buf.length
210 if (contentLength > 0) {
211 logger.error("partial input stream");
213 // chunked response --
214 // dropped connection is expected
215 response = new String(buf, 0, offset);
222 logger.error("unknown content-type: " + contentType);
225 } catch (Exception e) {
226 String message = "Parsing Content-Type: "
227 + connection.getContentType();
228 logger.error(message, e);
232 logger.error(connection.getResponseCode() + " "
233 + connection.getResponseMessage());
235 } catch (Exception e) {
237 "Exception in 'PolicyGuardXacmlHelper.callRESTfulPDP'", e);
241 // Connection may have failed or not been 200 OK, return Indeterminate
243 if(response == null || response.isEmpty()){
244 return Util.INDETERMINATE;
247 rawDecision = new JSONObject(response).getString("decision");
252 public static PolicyGuardResponse ParseXacmlPdpResponse(
253 com.att.research.xacml.api.Response xacmlResponse) {
255 if (xacmlResponse == null) {
258 // In case the actual XACML response was null, create an empty
259 // response object with decision "Indeterminate"
261 return new PolicyGuardResponse("Indeterminate", null, "");
264 Iterator<Result> it_res = xacmlResponse.getResults().iterator();
266 Result res = it_res.next();
267 String decision_from_xacml_response = res.getDecision().toString();
268 Iterator<AttributeCategory> it_attr_cat = res.getAttributes()
270 UUID req_id_from_xacml_response = null;
271 String operation_from_xacml_response = "";
273 while (it_attr_cat.hasNext()) {
274 Iterator<Attribute> it_attr = it_attr_cat.next().getAttributes()
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());
285 if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id"
287 Iterator<AttributeValue<?>> it_values = current_attr
288 .getValues().iterator();
289 operation_from_xacml_response = it_values.next().getValue()
296 return new PolicyGuardResponse(decision_from_xacml_response,
297 req_id_from_xacml_response, operation_from_xacml_response);
301 private void init(Properties properties) {
302 // used to store error messages
303 StringBuilder sb = new StringBuilder();
305 // fetch these parameters, if they exist
306 String timeoutString = properties.getProperty("pdpx.timeout");
307 String disabledString = properties.getProperty("guard.disabled");
309 if (disabledString != null) {
310 // decode optional 'guard.disabled' parameter
311 disabled = new Boolean(disabledString);
313 // skip everything else
319 * Decode 'pdpx.*' parameters
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");
333 // now, see which numeric entries (1-9) exist
334 ArrayList<URLEntry> entries = new ArrayList<>();
336 for (int index = 0; index < 10; index += 1) {
337 String urlPrefix = "guard.";
338 String pdpxPrefix = "pdpx.";
340 urlPrefix = urlPrefix + index + ".";
343 // see if the associated URL exists
344 String restURLlist = properties.getProperty(urlPrefix + "url");
345 if (nullOrEmpty(restURLlist)) {
346 // no entry for this index
350 // support a list of entries separated by semicolons. Each entry
355 for (String restURL : restURLlist.split("\\s*;\\s*")) {
356 String[] segments = restURL.split("\\s*,\\s*");
358 String password = null;
360 if (segments.length >= 2) {
361 // user id is provided
362 restURL = segments[0];
364 if (segments.length >= 3) {
365 // password is also provided
366 password = segments[2];
370 // URL does exist -- create the entry
371 URLEntry urlEntry = new URLEntry();
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)
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);
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",
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());
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)
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;
421 // include this URLEntry in the list
422 entries.add(urlEntry);
426 if (entries.size() == 0) {
427 sb.append("'pdpx.*' -- no URLs specified, ");
429 restUrls = entries.toArray(new URLEntry[0]);
432 if (timeoutString != null) {
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());
443 // if there are any errors, update 'errorMessage' & disable guard
445 if (sb.length() != 0) {
446 // remove the terminating ", ", and extract resulting error message
447 sb.setLength(sb.length() - 2);
448 errorMessage = sb.toString();
450 logger.error("Initialization failure: " + errorMessage);
455 * Check if a string is null or an empty string
458 * the string to be tested
459 * @return 'true' if the string is 'null' or has a length of 0, 'false'
462 static private boolean nullOrEmpty(String value) {
463 return (value == null || value.isEmpty());