| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| |
| /* |
| * AT&T - PROPRIETARY |
| * THIS FILE CONTAINS PROPRIETARY INFORMATION OF |
| * AT&T AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN |
| * ACCORDANCE WITH APPLICABLE AGREEMENTS. |
| * |
| * Copyright (c) 2013 AT&T Knowledge Ventures |
| * Unpublished and Not for Publication |
| * All Rights Reserved |
| */ |
| package org.apache.openaz.xacml.rest; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.ConnectException; |
| import java.net.HttpURLConnection; |
| import java.net.MalformedURLException; |
| import java.net.SocketTimeoutException; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.net.UnknownHostException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Scanner; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import javax.servlet.Servlet; |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.annotation.WebInitParam; |
| import javax.servlet.annotation.WebServlet; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.openaz.xacml.api.pap.PAPEngine; |
| import org.apache.openaz.xacml.api.pap.PAPEngineFactory; |
| import org.apache.openaz.xacml.api.pap.PAPException; |
| import org.apache.openaz.xacml.api.pap.PDP; |
| import org.apache.openaz.xacml.api.pap.PDPGroup; |
| import org.apache.openaz.xacml.api.pap.PDPPolicy; |
| import org.apache.openaz.xacml.api.pap.PDPStatus; |
| import org.apache.openaz.xacml.std.pap.StdPDP; |
| import org.apache.openaz.xacml.std.pap.StdPDPGroup; |
| import org.apache.openaz.xacml.std.pap.StdPDPItemSetChangeNotifier.StdItemSetChangeListener; |
| import org.apache.openaz.xacml.std.pap.StdPDPStatus; |
| import org.apache.openaz.xacml.util.FactoryException; |
| import org.apache.openaz.xacml.util.XACMLProperties; |
| |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import com.google.common.base.Splitter; |
| |
| /** |
| * Servlet implementation class XacmlPapServlet |
| */ |
| @WebServlet(description = "Implements the XACML PAP RESTful API.", urlPatterns = { |
| "/" |
| }, loadOnStartup = 1, initParams = { |
| @WebInitParam(name = "XACML_PROPERTIES_NAME", value = "xacml.pap.properties", description = "The location of the properties file holding configuration information.") |
| }) |
| public class XACMLPapServlet extends HttpServlet implements StdItemSetChangeListener, Runnable { |
| private static final long serialVersionUID = 1L; |
| private static final Log logger = LogFactory.getLog(XACMLPapServlet.class); |
| |
| /* |
| * papEngine - This is our engine workhorse that manages the PDP Groups and Nodes. |
| */ |
| private PAPEngine papEngine = null; |
| |
| /* |
| * This PAP instance's own URL. Need this when creating URLs to send to the PDPs so they can GET the |
| * Policy files from this process. |
| */ |
| private static String papURL = null; |
| |
| /* |
| * List of Admin Console URLs. Used to send notifications when configuration changes. The |
| * CopyOnWriteArrayList *should* protect from concurrency errors. This list is seldom changed but often |
| * read, so the costs of this approach make sense. |
| */ |
| private static final CopyOnWriteArrayList<String> adminConsoleURLStringList = new CopyOnWriteArrayList<String>(); |
| |
| /* |
| * This thread may be invoked upon startup to initiate sending PDP policy/pip configuration when this |
| * servlet starts. Its configurable by the admin. |
| */ |
| private Thread initiateThread = null; |
| |
| /* |
| * // The heartbeat thread. |
| */ |
| private static Heartbeat heartbeat = null; |
| private static Thread heartbeatThread = null; |
| |
| /** |
| * @see HttpServlet#HttpServlet() |
| */ |
| public XACMLPapServlet() { |
| super(); |
| } |
| |
| /** |
| * @see Servlet#init(ServletConfig) |
| */ |
| @Override |
| public void init(ServletConfig config) throws ServletException { |
| try { |
| // |
| // Initialize |
| // |
| XACMLRest.xacmlInit(config); |
| // |
| // Load the properties |
| // |
| XACMLRest.loadXacmlProperties(null, null); |
| // |
| // Load our PAP engine, first create a factory |
| // |
| PAPEngineFactory factory = PAPEngineFactory.newInstance(XACMLProperties |
| .getProperty(XACMLProperties.PROP_PAP_PAPENGINEFACTORY)); |
| // |
| // The factory knows how to go about creating a PAP Engine |
| // |
| this.papEngine = factory.newEngine(); |
| // |
| // we are about to call the PDPs and give them their configuration. |
| // To do that we need to have the URL of this PAP so we can construct the Policy file URLs |
| // |
| XACMLPapServlet.papURL = XACMLProperties.getProperty(XACMLRestProperties.PROP_PAP_URL); |
| // |
| // Sanity check that a URL was defined somewhere, its essential. |
| // |
| // How to check that its valid? We can validate the form, but since we are in the init() method we |
| // are not fully loaded yet so we really couldn't ping ourself to see if the URL will work. One |
| // will have to look for errors in the PDP logs to determine if they are failing to initiate a |
| // request to this servlet. |
| // |
| if (XACMLPapServlet.papURL == null) { |
| throw new PAPException("The property " + XACMLRestProperties.PROP_PAP_URL + " is not valid: " |
| + XACMLPapServlet.papURL); |
| } |
| // |
| // Configurable - have the PAP servlet initiate sending the latest PDP policy/pip configuration |
| // to all its known PDP nodes. |
| // |
| // Note: parseBoolean will return false if there is no property defined. This is fine for a |
| // default. |
| // |
| if (Boolean.parseBoolean(XACMLProperties |
| .getProperty(XACMLRestProperties.PROP_PAP_INITIATE_PDP_CONFIG))) { |
| this.initiateThread = new Thread(this); |
| this.initiateThread.start(); |
| } |
| // |
| // After startup, the PAP does Heartbeats to each of the PDPs periodically |
| // |
| XACMLPapServlet.heartbeat = new Heartbeat(this.papEngine); |
| XACMLPapServlet.heartbeatThread = new Thread(XACMLPapServlet.heartbeat); |
| XACMLPapServlet.heartbeatThread.start(); |
| } catch (FactoryException | PAPException e) { |
| logger.error("Failed to create engine", e); |
| throw new ServletException("PAP not initialized; error: " + e); |
| } catch (Exception e) { |
| logger.error("Failed to create engine - unexpected error: ", e); |
| throw new ServletException("PAP not initialized; unexpected error: " + e); |
| } |
| } |
| |
| /** |
| * Thread used only during PAP startup to initiate change messages to all known PDPs. This must be on a |
| * separate thread so that any GET requests from the PDPs during this update can be serviced. |
| */ |
| @Override |
| public void run() { |
| // |
| // send the current configuration to all the PDPs that we know about |
| // |
| changed(); |
| } |
| |
| /** |
| * @see Servlet#destroy() Depending on how this servlet is run, we may or may not care about cleaning up |
| * the resources. For now we assume that we do care. |
| */ |
| @Override |
| public void destroy() { |
| // |
| // Make sure our threads are destroyed |
| // |
| if (XACMLPapServlet.heartbeatThread != null) { |
| // |
| // stop the heartbeat |
| // |
| try { |
| if (XACMLPapServlet.heartbeat != null) { |
| XACMLPapServlet.heartbeat.terminate(); |
| } |
| XACMLPapServlet.heartbeatThread.interrupt(); |
| XACMLPapServlet.heartbeatThread.join(); |
| } catch (InterruptedException e) { |
| logger.error(e); |
| } |
| } |
| if (this.initiateThread != null) { |
| try { |
| this.initiateThread.interrupt(); |
| this.initiateThread.join(); |
| } catch (InterruptedException e) { |
| logger.error(e); |
| } |
| } |
| } |
| |
| /** |
| * Called by: - PDP nodes to register themselves with the PAP, and - Admin Console to make changes in the |
| * PDP Groups. |
| * |
| * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) |
| */ |
| @Override |
| protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, |
| IOException { |
| try { |
| |
| XACMLRest.dumpRequest(request); |
| |
| // since getParameter reads the content string, explicitly get the content before doing that. |
| // Simply getting the inputStream seems to protect it against being consumed by getParameter. |
| request.getInputStream(); |
| |
| // |
| // Is this from the Admin Console? |
| // |
| String groupId = request.getParameter("groupId"); |
| if (groupId != null) { |
| // |
| // this is from the Admin Console, so handle separately |
| // |
| doACPost(request, response, groupId); |
| return; |
| } |
| |
| // |
| // Request is from a PDP. |
| // It is coming up and asking for its config |
| // |
| |
| // |
| // Get the PDP's ID |
| // |
| String id = this.getPDPID(request); |
| logger.info("doPost from: " + id); |
| // |
| // Get the PDP Object |
| // |
| PDP pdp = this.papEngine.getPDP(id); |
| // |
| // Is it known? |
| // |
| if (pdp == null) { |
| logger.info("Unknown PDP: " + id); |
| try { |
| this.papEngine.newPDP(id, this.papEngine.getDefaultGroup(), id, |
| "Registered on first startup"); |
| } catch (NullPointerException | PAPException e) { |
| logger.error("Failed to create new PDP", e); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); |
| return; |
| } |
| // get the PDP we just created |
| pdp = this.papEngine.getPDP(id); |
| if (pdp == null) { |
| String message = "Failed to create new PDP for id: " + id; |
| logger.error(message); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message); |
| return; |
| } |
| } |
| // |
| // Get the PDP's Group |
| // |
| PDPGroup group = this.papEngine.getPDPGroup(pdp); |
| if (group == null) { |
| response.sendError(HttpServletResponse.SC_UNAUTHORIZED, |
| "PDP not associated with any group, even the default"); |
| return; |
| } |
| // |
| // Determine what group the PDP node is in and get |
| // its policy/pip properties. |
| // |
| Properties policies = group.getPolicyProperties(); |
| Properties pipconfig = group.getPipConfigProperties(); |
| // |
| // Get the current policy/pip configuration that the PDP has |
| // |
| Properties pdpProperties = new Properties(); |
| pdpProperties.load(request.getInputStream()); |
| logger.info("PDP Current Properties: " + pdpProperties.toString()); |
| logger.info("Policies: " + (policies != null ? policies.toString() : "null")); |
| logger.info("Pip config: " + (pipconfig != null ? pipconfig.toString() : "null")); |
| // |
| // Validate the node's properties |
| // |
| boolean isCurrent = this.isPDPCurrent(policies, pipconfig, pdpProperties); |
| // |
| // Send back current configuration |
| // |
| if (!isCurrent) { |
| // |
| // Tell the PDP we are sending back the current policies/pip config |
| // |
| logger.info("PDP configuration NOT current."); |
| if (policies != null) { |
| // |
| // Put URL's into the properties in case the PDP needs to |
| // retrieve them. |
| // |
| this.populatePolicyURL(request.getRequestURL(), policies); |
| // |
| // Copy the properties to the output stream |
| // |
| policies.store(response.getOutputStream(), ""); |
| } |
| if (pipconfig != null) { |
| // |
| // Copy the properties to the output stream |
| // |
| pipconfig.store(response.getOutputStream(), ""); |
| } |
| // |
| // We are good - and we are sending them information |
| // |
| response.setStatus(HttpServletResponse.SC_OK); |
| // TODO - Correct? |
| setPDPSummaryStatus(pdp, PDPStatus.Status.OUT_OF_SYNCH); |
| } else { |
| // |
| // Tell them they are good |
| // |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| |
| // TODO - Correct? |
| setPDPSummaryStatus(pdp, PDPStatus.Status.UP_TO_DATE); |
| |
| } |
| // |
| // tell the AC that something changed |
| // |
| notifyAC(); |
| } catch (PAPException e) { |
| logger.debug("POST exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| } |
| |
| /** |
| * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) |
| */ |
| @Override |
| protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, |
| IOException { |
| try { |
| XACMLRest.dumpRequest(request); |
| |
| // Is this from the Admin Console? |
| String groupId = request.getParameter("groupId"); |
| if (groupId != null) { |
| // this is from the Admin Console, so handle separately |
| doACGet(request, response, groupId); |
| return; |
| } |
| // |
| // Get the PDP's ID |
| // |
| String id = this.getPDPID(request); |
| logger.info("doGet from: " + id); |
| // |
| // Get the PDP Object |
| // |
| PDP pdp = this.papEngine.getPDP(id); |
| // |
| // Is it known? |
| // |
| if (pdp == null) { |
| // |
| // Check if request came from localhost |
| // |
| String message = "Unknown PDP: " + id + " from " + request.getRemoteHost() + " us: " |
| + request.getLocalAddr(); |
| logger.info(message); |
| if (request.getRemoteHost().equals("localhost") |
| || request.getRemoteHost().equals("127.0.0.1") //NOPMD |
| || request.getRemoteHost().equals(request.getLocalAddr())) { |
| // |
| // Return status information - basically all the groups |
| // |
| Set<PDPGroup> groups = papEngine.getPDPGroups(); |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), groups); |
| response.setHeader("content-type", "application/json"); |
| response.setStatus(HttpServletResponse.SC_OK); |
| return; |
| } |
| response.sendError(HttpServletResponse.SC_UNAUTHORIZED, message); |
| return; |
| } |
| // |
| // Get the PDP's Group |
| // |
| PDPGroup group = this.papEngine.getPDPGroup(pdp); |
| if (group == null) { |
| String message = "No group associated with pdp " + pdp.getId(); |
| logger.warn(message); |
| response.sendError(HttpServletResponse.SC_UNAUTHORIZED, message); |
| return; |
| } |
| // |
| // Which policy do they want? |
| // |
| String policyId = request.getParameter("id"); |
| if (policyId == null) { |
| String message = "Did not specify an id for the policy"; |
| logger.warn(message); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, message); |
| return; |
| } |
| PDPPolicy policy = group.getPolicy(policyId); |
| if (policy == null) { |
| String message = "Unknown policy: " + policyId; |
| logger.warn(message); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, message); |
| return; |
| } |
| // |
| // Get its stream |
| // |
| try (InputStream is = policy.getStream(); OutputStream os = response.getOutputStream()) { |
| // |
| // Send the policy back |
| // |
| IOUtils.copy(is, os); |
| |
| response.setStatus(HttpServletResponse.SC_OK); |
| } catch (PAPException e) { |
| String message = "Failed to open policy id " + policyId; |
| logger.error(message); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, message); |
| } |
| } catch (PAPException e) { |
| logger.error("GET exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| } |
| |
| protected String getPDPID(HttpServletRequest request) { |
| String pdpURL = request.getHeader(XACMLRestProperties.PROP_PDP_HTTP_HEADER_ID); |
| if (pdpURL == null || pdpURL.isEmpty()) { |
| // |
| // Should send back its port for identification |
| // |
| logger.warn("PDP did not send custom header"); |
| pdpURL = ""; |
| } |
| return pdpURL; |
| } |
| |
| private boolean isPDPCurrent(Properties policies, Properties pipconfig, Properties pdpProperties) { |
| String localRootPolicies = policies.getProperty(XACMLProperties.PROP_ROOTPOLICIES); |
| String localReferencedPolicies = policies.getProperty(XACMLProperties.PROP_REFERENCEDPOLICIES); |
| if (localRootPolicies == null || localReferencedPolicies == null) { |
| logger.warn("Missing property on PAP server: RootPolicies=" + localRootPolicies |
| + " ReferencedPolicies=" + localReferencedPolicies); |
| return false; |
| } |
| // |
| // Compare the policies and pipconfig properties to the pdpProperties |
| // |
| try { |
| // |
| // the policy properties includes only xacml.rootPolicies and |
| // xacml.referencedPolicies without any .url entries |
| // |
| Properties pdpPolicies = XACMLProperties.getPolicyProperties(pdpProperties, false); |
| Properties pdpPipConfig = XACMLProperties.getPipProperties(pdpProperties); |
| if (localRootPolicies.equals(pdpPolicies.getProperty(XACMLProperties.PROP_ROOTPOLICIES)) |
| && localReferencedPolicies.equals(pdpPolicies |
| .getProperty(XACMLProperties.PROP_REFERENCEDPOLICIES)) && pdpPipConfig.equals(pipconfig)) { |
| // |
| // The PDP is current |
| // |
| return true; |
| } |
| } catch (Exception e) { //NOPMD |
| // we get here if the PDP did not include either xacml.rootPolicies or xacml.pip.engines, |
| // or if there are policies that do not have a corresponding ".url" property. |
| // Either of these cases means that the PDP is not up-to-date, so just drop-through to return |
| // false. |
| } |
| return false; |
| } |
| |
| private void populatePolicyURL(StringBuffer urlPath, Properties policies) { |
| String lists[] = new String[2]; |
| lists[0] = policies.getProperty(XACMLProperties.PROP_ROOTPOLICIES); |
| lists[1] = policies.getProperty(XACMLProperties.PROP_REFERENCEDPOLICIES); |
| for (String list : lists) { |
| if (list != null && !list.isEmpty()) { |
| for (String id : Splitter.on(',').trimResults().omitEmptyStrings().split(list)) { |
| String url = urlPath + "?id=" + id; |
| logger.info("Policy URL for " + id + ": " + url); |
| policies.setProperty(id + ".url", url); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @see HttpServlet#doPut(HttpServletRequest request, HttpServletResponse response) |
| */ |
| @Override |
| protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, |
| IOException { |
| XACMLRest.dumpRequest(request); |
| // |
| // since getParameter reads the content string, explicitly get the content before doing that. |
| // Simply getting the inputStream seems to protect it against being consumed by getParameter. |
| // |
| request.getInputStream(); |
| // |
| // See if this is Admin Console registering itself with us |
| // |
| String acURLString = request.getParameter("adminConsoleURL"); |
| if (acURLString != null) { |
| // |
| // remember this Admin Console for future updates |
| // |
| if (!adminConsoleURLStringList.contains(acURLString)) { |
| adminConsoleURLStringList.add(acURLString); |
| } |
| if (logger.isDebugEnabled()) { |
| logger.debug("Admin Console registering with URL: " + acURLString); |
| } |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| return; |
| } |
| // |
| // Is this some other operation from the Admin Console? |
| // |
| String groupId = request.getParameter("groupId"); |
| if (groupId != null) { |
| // |
| // this is from the Admin Console, so handle separately |
| // |
| doACPut(request, response, groupId); |
| return; |
| } |
| // |
| // We do not expect anything from anywhere else. |
| // This method is here in case we ever need to support other operations. |
| // |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request does not have groupId"); |
| } |
| |
| /** |
| * @see HttpServlet#doDelete(HttpServletRequest request, HttpServletResponse response) |
| */ |
| @Override |
| protected void doDelete(HttpServletRequest request, HttpServletResponse response) |
| throws ServletException, IOException { |
| XACMLRest.dumpRequest(request); |
| // |
| // Is this from the Admin Console? |
| // |
| String groupId = request.getParameter("groupId"); |
| if (groupId != null) { |
| // |
| // this is from the Admin Console, so handle separately |
| // |
| doACDelete(request, response, groupId); |
| return; |
| } |
| // |
| // We do not expect anything from anywhere else. |
| // This method is here in case we ever need to support other operations. |
| // |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request does not have groupId"); |
| } |
| |
| // |
| // Admin Console request handling |
| // |
| |
| /** |
| * Requests from the Admin Console to GET info about the Groups and PDPs |
| * |
| * @param request |
| * @param response |
| * @param groupId |
| * @throws ServletException |
| * @throws java.io.IOException |
| */ |
| private void doACGet(HttpServletRequest request, HttpServletResponse response, String groupId) |
| throws ServletException, IOException { |
| try { |
| String parameterDefault = request.getParameter("default"); |
| String pdpId = request.getParameter("pdpId"); |
| String pdpGroup = request.getParameter("getPDPGroup"); |
| if ("".equals(groupId)) { |
| // request IS from AC but does not identify a group by name |
| if (parameterDefault != null) { |
| // Request is for the Default group (whatever its id) |
| PDPGroup group = papEngine.getDefaultGroup(); |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), group); |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("GET Default group req from '" + request.getRequestURL() + "'"); |
| } |
| response.setStatus(HttpServletResponse.SC_OK); |
| response.setHeader("content-type", "application/json"); |
| response.getOutputStream().close(); |
| return; |
| |
| } else if (pdpId != null) { |
| // Request is related to a PDP |
| if (pdpGroup == null) { |
| // Request is for the PDP itself |
| // Request is for the (unspecified) group containing a given PDP |
| PDP pdp = papEngine.getPDP(pdpId); |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), pdp); |
| |
| if (logger.isDebugEnabled()) { |
| logger |
| .debug("GET pdp '" + pdpId + "' req from '" + request.getRequestURL() + "'"); |
| } |
| response.setStatus(HttpServletResponse.SC_OK); |
| response.setHeader("content-type", "application/json"); |
| response.getOutputStream().close(); |
| return; |
| |
| } else { |
| // Request is for the (unspecified) group containing a given PDP |
| PDP pdp = papEngine.getPDP(pdpId); |
| PDPGroup group = papEngine.getPDPGroup(pdp); |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), group); |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("GET PDP '" + pdpId + "' Group req from '" + request.getRequestURL() |
| + "'"); |
| } |
| response.setStatus(HttpServletResponse.SC_OK); |
| response.setHeader("content-type", "application/json"); |
| response.getOutputStream().close(); |
| return; |
| } |
| |
| } else { |
| // request is for top-level properties about all groups |
| Set<PDPGroup> groups = papEngine.getPDPGroups(); |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), groups); |
| |
| // TODO |
| // In "notification" section, ALSO need to tell AC about other changes (made by other |
| // ACs)?' |
| // TODO add new PDP notification (or just "config changed" notification) in appropriate |
| // place |
| if (logger.isDebugEnabled()) { |
| logger.debug("GET All groups req"); |
| } |
| response.setStatus(HttpServletResponse.SC_OK); |
| response.setHeader("content-type", "application/json"); |
| response.getOutputStream().close(); |
| return; |
| } |
| } |
| |
| // for all other GET operations the group must exist before the operation can be done |
| PDPGroup group = papEngine.getGroup(groupId); |
| if (group == null) { |
| logger.error("Unknown groupId '" + groupId + "'"); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown groupId '" + groupId + "'"); |
| return; |
| } |
| |
| // Figure out which request this is based on the parameters |
| String policyId = request.getParameter("policyId"); |
| |
| if (policyId != null) { |
| // // retrieve a policy |
| // PDPPolicy policy = papEngine.getPDPPolicy(policyId); |
| // |
| // // convert response object to JSON and include in the response |
| // ObjectMapper mapper = new ObjectMapper(); |
| // mapper.writeValue(response.getOutputStream(), pdp); |
| // |
| // logger.debug("GET group '" + group.getId() + "' req from '" + request.getRequestURL() + |
| // "'"); |
| // response.setStatus(HttpServletResponse.SC_OK); |
| // response.setHeader("content-type", "application/json"); |
| // response.getOutputStream().close(); |
| // return; |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "GET Policy not implemented"); |
| |
| } else { |
| // No other parameters, so return the identified Group |
| |
| // convert response object to JSON and include in the response |
| ObjectMapper mapper = new ObjectMapper(); |
| mapper.writeValue(response.getOutputStream(), group); |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("GET group '" + group.getId() + "' req from '" + request.getRequestURL() |
| + "'"); |
| } |
| response.setStatus(HttpServletResponse.SC_OK); |
| response.setHeader("content-type", "application/json"); |
| response.getOutputStream().close(); |
| return; |
| } |
| |
| // |
| // Currently there are no other GET calls from the AC. |
| // The AC uses the "GET All Groups" operation to fill its local cache and uses that cache for all |
| // other GETs without calling the PAP. |
| // Other GETs that could be called: |
| // Specific Group (groupId=<groupId>) |
| // A Policy (groupId=<groupId> policyId=<policyId>) |
| // A PDP (groupId=<groupId> pdpId=<pdpId>) |
| |
| // TODO - implement other GET operations if needed |
| |
| logger.error("UNIMPLEMENTED "); |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "UNIMPLEMENTED"); |
| } catch (PAPException e) { |
| logger.error("AC Get exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| |
| } |
| |
| /** |
| * Requests from the Admin Console for operations not on single specific objects |
| * |
| * @param request |
| * @param response |
| * @param groupId |
| * @throws ServletException |
| * @throws java.io.IOException |
| */ |
| private void doACPost(HttpServletRequest request, HttpServletResponse response, String groupId) |
| throws ServletException, IOException { |
| try { |
| String groupName = request.getParameter("groupName"); |
| String groupDescription = request.getParameter("groupDescription"); |
| if (groupName != null && groupDescription != null) { |
| // Args: group=<groupId> groupName=<name> groupDescription=<description> <= create a new group |
| String unescapedName = URLDecoder.decode(groupName, "UTF-8"); |
| String unescapedDescription = URLDecoder.decode(groupDescription, "UTF-8"); |
| try { |
| papEngine.newGroup(unescapedName, unescapedDescription); |
| } catch (Exception e) { |
| logger.error("Unable to create new group: " + e.getLocalizedMessage()); |
| response.sendError(500, "Unable to create new group '" + groupId + "'"); |
| return; |
| } |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("New Group '" + groupId + "' created"); |
| } |
| // tell the Admin Consoles there is a change |
| notifyAC(); |
| // new group by definition has no PDPs, so no need to notify them of changes |
| return; |
| } |
| |
| // for all remaining POST operations the group must exist before the operation can be done |
| PDPGroup group = papEngine.getGroup(groupId); |
| if (group == null) { |
| logger.error("Unknown groupId '" + groupId + "'"); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown groupId '" + groupId + "'"); |
| return; |
| } |
| |
| // determine the operation needed based on the parameters in the request |
| if (request.getParameter("policyId") != null) { |
| // Args: group=<groupId> policy=<policyId> <= copy file |
| // copy a policy from the request contents into a file in the group's directory on this |
| // machine |
| String policyId = request.getParameter("policyId"); |
| try { |
| ((StdPDPGroup)group).copyPolicyToFile(policyId, request.getInputStream()); |
| } catch (Exception e) { |
| String message = "Policy '" + policyId + "' not copied to group '" + groupId + "': " + e; |
| logger.error(message); |
| response.sendError(500, message); |
| return; |
| } |
| // policy file copied ok |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("policy '" + policyId + "' copied to directory for group '" + groupId + "'"); |
| } |
| return; |
| |
| } else if (request.getParameter("default") != null) { |
| // Args: group=<groupId> default=true <= make default |
| // change the current default group to be the one identified in the request. |
| // |
| // This is a POST operation rather than a PUT "update group" because of the side-effect that |
| // the current default group is also changed. |
| // It should never be the case that multiple groups are currently marked as the default, but |
| // protect against that anyway. |
| try { |
| papEngine.SetDefaultGroup(group); |
| } catch (Exception e) { |
| logger.error("Unable to set group: " + e.getLocalizedMessage()); |
| response.sendError(500, "Unable to set group '" + groupId + "' to default"); |
| return; |
| } |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Group '" + groupId + "' set to be default"); |
| } |
| // Notify the Admin Consoles that something changed |
| // For now the AC cannot handle anything more detailed than the whole set of PDPGroups, so |
| // just notify on that |
| // TODO - Future: FIGURE OUT WHAT LEVEL TO NOTIFY: 2 groups or entire set - currently notify |
| // AC to update whole configuration of all groups |
| notifyAC(); |
| // This does not affect any PDPs in the existing groups, so no need to notify them of this |
| // change |
| return; |
| |
| } else if (request.getParameter("pdpId") != null) { |
| // Args: group=<groupId> pdpId=<pdpId> <= move PDP to group |
| String pdpId = request.getParameter("pdpId"); |
| PDP pdp = papEngine.getPDP(pdpId); |
| |
| PDPGroup originalGroup = papEngine.getPDPGroup(pdp); |
| |
| papEngine.movePDP(pdp, group); |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("PDP '" + pdp.getId() + "' moved to group '" + group.getId() |
| + "' set to be default"); |
| } |
| |
| // update the status of both the original group and the new one |
| ((StdPDPGroup)originalGroup).resetStatus(); |
| ((StdPDPGroup)group).resetStatus(); |
| |
| // Notify the Admin Consoles that something changed |
| // For now the AC cannot handle anything more detailed than the whole set of PDPGroups, so |
| // just notify on that |
| notifyAC(); |
| // Need to notify the PDP that it's config may have changed |
| pdpChanged(pdp); |
| return; |
| |
| } |
| } catch (PAPException e) { |
| logger.error("AC POST exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| } |
| |
| /** |
| * Requests from the Admin Console to create new items or update existing ones |
| * |
| * @param request |
| * @param response |
| * @param groupId |
| * @throws ServletException |
| * @throws java.io.IOException |
| */ |
| private void doACPut(HttpServletRequest request, HttpServletResponse response, String groupId) |
| throws ServletException, IOException { |
| try { |
| |
| // for PUT operations the group may or may not need to exist before the operation can be done |
| PDPGroup group = papEngine.getGroup(groupId); |
| |
| // determine the operation needed based on the parameters in the request |
| |
| // for remaining operations the group must exist before the operation can be done |
| if (group == null) { |
| logger.error("Unknown groupId '" + groupId + "'"); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown groupId '" + groupId + "'"); |
| return; |
| } |
| if (request.getParameter("policy") != null) { |
| // group=<groupId> policy=<policyId> contents=policy file <= Create new policy file in group |
| // dir, or replace it if it already exists (do not touch properties) |
| // TODO - currently this is done by the AC, but it should be done here by getting the policy |
| // file out of the contents and saving to disk |
| logger.error("PARTIALLY IMPLEMENTED!!! ACTUAL CHANGES SHOULD BE MADE BY PAP SERVLET!!! "); |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| return; |
| } else if (request.getParameter("pdpId") != null) { |
| // ARGS: group=<groupId> pdpId=<pdpId/URL> <= create a new PDP or Update an Existing one |
| |
| String pdpId = request.getParameter("pdpId"); |
| |
| // get the request content into a String |
| String json = null; |
| // read the inputStream into a buffer (trick found online scans entire input looking for |
| // end-of-file) |
| Scanner scanner = new Scanner(request.getInputStream()); |
| scanner.useDelimiter("\\A"); |
| json = scanner.hasNext() ? scanner.next() : ""; |
| scanner.close(); |
| logger.info("JSON request from AC: " + json); |
| |
| // convert Object sent as JSON into local object |
| ObjectMapper mapper = new ObjectMapper(); |
| |
| Object objectFromJSON = mapper.readValue(json, StdPDP.class); |
| |
| if (pdpId == null || objectFromJSON == null || !(objectFromJSON instanceof StdPDP) |
| || ((StdPDP)objectFromJSON).getId() == null |
| || !((StdPDP)objectFromJSON).getId().equals(pdpId)) { |
| logger.error("PDP new/update had bad input. pdpId=" + pdpId + " objectFromJSON=" |
| + objectFromJSON); |
| response.sendError(500, "Bad input, pdpid=" + pdpId + " object=" + objectFromJSON); |
| } |
| StdPDP pdp = (StdPDP)objectFromJSON; |
| |
| if (papEngine.getPDP(pdpId) == null) { |
| // this is a request to create a new PDP object |
| papEngine.newPDP(pdp.getId(), group, pdp.getName(), pdp.getDescription()); |
| } else { |
| // this is a request to update the pdp |
| papEngine.updatePDP(pdp); |
| } |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("PDP '" + pdpId + "' created/updated"); |
| } |
| |
| // adjust the group's state including the new PDP |
| ((StdPDPGroup)group).resetStatus(); |
| |
| // tell the Admin Consoles there is a change |
| notifyAC(); |
| // this might affect the PDP, so notify it of the change |
| pdpChanged(pdp); |
| return; |
| } else if (request.getParameter("pipId") != null) { |
| // group=<groupId> pipId=<pipEngineId> contents=pip properties <= add a PIP to pip config, or |
| // replace it if it already exists (lenient operation) |
| // TODO |
| logger.error("UNIMPLEMENTED "); |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "UNIMPLEMENTED"); |
| return; |
| } else { |
| // Assume that this is an update of an existing PDP Group |
| // ARGS: group=<groupId> <= Update an Existing Group |
| |
| // get the request content into a String |
| String json = null; |
| // read the inputStream into a buffer (trick found online scans entire input looking for |
| // end-of-file) |
| Scanner scanner = new Scanner(request.getInputStream()); |
| scanner.useDelimiter("\\A"); |
| json = scanner.hasNext() ? scanner.next() : ""; |
| scanner.close(); |
| logger.info("JSON request from AC: " + json); |
| |
| // convert Object sent as JSON into local object |
| ObjectMapper mapper = new ObjectMapper(); |
| |
| Object objectFromJSON = mapper.readValue(json, StdPDPGroup.class); |
| |
| if (objectFromJSON == null || !(objectFromJSON instanceof StdPDPGroup) |
| || !((StdPDPGroup)objectFromJSON).getId().equals(group.getId())) { |
| logger.error("Group update had bad input. id=" + group.getId() + " objectFromJSON=" |
| + objectFromJSON); |
| response.sendError(500, "Bad input, id=" + group.getId() + " object=" + objectFromJSON); |
| } |
| |
| // The Path on the PAP side is not carried on the RESTful interface with the AC |
| // (because it is local to the PAP) |
| // so we need to fill that in before submitting the group for update |
| ((StdPDPGroup)objectFromJSON).setDirectory(((StdPDPGroup)group).getDirectory()); |
| |
| papEngine.updateGroup((StdPDPGroup)objectFromJSON); |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| if (logger.isDebugEnabled()) { |
| logger.debug("Group '" + group.getId() + "' updated"); |
| } |
| // tell the Admin Consoles there is a change |
| notifyAC(); |
| // Group changed, which might include changing the policies |
| groupChanged(group); |
| return; |
| } |
| } catch (PAPException e) { |
| logger.error("AC PUT exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| } |
| |
| /** |
| * Requests from the Admin Console to delete/remove items |
| * |
| * @param request |
| * @param response |
| * @param groupId |
| * @throws ServletException |
| * @throws java.io.IOException |
| */ |
| private void doACDelete(HttpServletRequest request, HttpServletResponse response, String groupId) |
| throws ServletException, IOException { |
| try { |
| // for all DELETE operations the group must exist before the operation can be done |
| PDPGroup group = papEngine.getGroup(groupId); |
| if (group == null) { |
| logger.error("Unknown groupId '" + groupId + "'"); |
| response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown groupId '" + groupId + "'"); |
| return; |
| } |
| // determine the operation needed based on the parameters in the request |
| if (request.getParameter("policy") != null) { |
| // group=<groupId> policy=<policyId> [delete=<true|false>] <= delete policy file from group |
| // TODO |
| logger.error("UNIMPLEMENTED "); |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "UNIMPLEMENTED"); |
| return; |
| } else if (request.getParameter("pdpId") != null) { |
| // ARGS: group=<groupId> pdpId=<pdpId> <= delete PDP |
| String pdpId = request.getParameter("pdpId"); |
| PDP pdp = papEngine.getPDP(pdpId); |
| |
| papEngine.removePDP(pdp); |
| |
| // adjust the status of the group, which may have changed when we removed this PDP |
| ((StdPDPGroup)group).resetStatus(); |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| notifyAC(); |
| |
| // update the PDP and tell it that it has NO Policies (which prevents it from serving PEP |
| // Requests) |
| pdpChanged(pdp); |
| return; |
| } else if (request.getParameter("pipId") != null) { |
| // group=<groupId> pipId=<pipEngineId> <= delete PIP config for given engine |
| // TODO |
| logger.error("UNIMPLEMENTED "); |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, "UNIMPLEMENTED"); |
| return; |
| } else { |
| // ARGS: group=<groupId> movePDPsToGroupId=<movePDPsToGroupId> <= delete a group and move all |
| // its PDPs to the given group |
| String moveToGroupId = request.getParameter("movePDPsToGroupId"); |
| PDPGroup moveToGroup = null; |
| if (moveToGroupId != null) { |
| moveToGroup = papEngine.getGroup(moveToGroupId); |
| } |
| |
| // get list of PDPs in the group being deleted so we can notify them that they got changed |
| Set<PDP> movedPDPs = new HashSet<PDP>(); |
| movedPDPs.addAll(group.getPdps()); |
| |
| // do the move/remove |
| papEngine.removeGroup(group, moveToGroup); |
| |
| response.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| notifyAC(); |
| // notify any PDPs in the removed set that their config may have changed |
| for (PDP pdp : movedPDPs) { |
| pdpChanged(pdp); |
| } |
| return; |
| } |
| |
| } catch (PAPException e) { |
| logger.error("AC DELETE exception: " + e, e); |
| response.sendError(500, e.getMessage()); |
| return; |
| } |
| } |
| |
| // |
| // Heartbeat thread - periodically check on PDPs' status |
| // |
| |
| /** |
| * Heartbeat with all known PDPs. Implementation note: The PDPs are contacted Sequentially, not in |
| * Parallel. If we did this in parallel using multiple threads we would simultaneously use - 1 thread and |
| * - 1 connection for EACH PDP. This could become a resource problem since we already use multiple threads |
| * and connections for updating the PDPs when user changes occur. Using separate threads can also make it |
| * tricky dealing with timeouts on PDPs that are non-responsive. The Sequential operation does a heartbeat |
| * request to each PDP one at a time. This has the flaw that any PDPs that do not respond will hold up the |
| * entire heartbeat sequence until they timeout. If there are a lot of non-responsive PDPs and the timeout |
| * is large-ish (the default is 20 seconds) it could take a long time to cycle through all of the PDPs. |
| * That means that this may not notice a PDP being down in a predictable time. |
| */ |
| private class Heartbeat implements Runnable { |
| private PAPEngine papEngine; |
| private Set<PDP> pdps = new HashSet<PDP>(); |
| private int heartbeatInterval; |
| private int heartbeatTimeout; |
| |
| public volatile boolean isRunning = false; |
| |
| public synchronized boolean isRunning() { |
| return this.isRunning; |
| } |
| |
| public synchronized void terminate() { |
| this.isRunning = false; |
| } |
| |
| public Heartbeat(PAPEngine engine) { |
| this.papEngine = engine; |
| this.heartbeatInterval = Integer.parseInt(XACMLProperties |
| .getProperty(XACMLRestProperties.PROP_PAP_HEARTBEAT_INTERVAL, "10000")); |
| this.heartbeatTimeout = Integer.parseInt(XACMLProperties |
| .getProperty(XACMLRestProperties.PROP_PAP_HEARTBEAT_TIMEOUT, "10000")); |
| } |
| |
| @Override |
| public void run() { |
| // |
| // Set ourselves as running |
| // |
| synchronized (this) { |
| this.isRunning = true; |
| } |
| HashMap<String, URL> idToURLMap = new HashMap<String, URL>(); |
| try { |
| while (this.isRunning()) { |
| // Wait the given time |
| Thread.sleep(heartbeatInterval); |
| |
| // get the list of PDPs (may have changed since last time) |
| pdps.clear(); |
| synchronized (papEngine) { |
| try { |
| for (PDPGroup g : papEngine.getPDPGroups()) { |
| for (PDP p : g.getPdps()) { |
| pdps.add(p); |
| } |
| } |
| } catch (PAPException e) { |
| logger |
| .error("Heartbeat unable to read PDPs from PAPEngine: " + e.getMessage(), e); |
| } |
| } |
| // |
| // Check for shutdown |
| // |
| if (!this.isRunning()) { |
| logger.info("isRunning is false, getting out of loop."); |
| break; |
| } |
| |
| // try to get the summary status from each PDP |
| boolean changeSeen = false; |
| for (PDP pdp : pdps) { |
| // |
| // Check for shutdown |
| // |
| if (!this.isRunning()) { |
| logger.info("isRunning is false, getting out of loop."); |
| break; |
| } |
| // the id of the PDP is its url (though we add a query parameter) |
| URL pdpURL = idToURLMap.get(pdp.getId()); |
| if (pdpURL == null) { |
| // haven't seen this PDP before |
| String fullURLString = null; |
| try { |
| fullURLString = pdp.getId() + "?type=hb"; |
| pdpURL = new URL(fullURLString); |
| idToURLMap.put(pdp.getId(), pdpURL); |
| } catch (MalformedURLException e) { |
| logger.error("PDP id '" + fullURLString + "' is not a valid URL: " + e, e); |
| continue; |
| } |
| } |
| |
| // Do a GET with type HeartBeat |
| String newStatus = ""; |
| |
| HttpURLConnection connection = null; |
| try { |
| |
| // |
| // Open up the connection |
| // |
| connection = (HttpURLConnection)pdpURL.openConnection(); |
| // |
| // Setup our method and headers |
| // |
| connection.setRequestMethod("GET"); |
| connection.setConnectTimeout(heartbeatTimeout); |
| // |
| // Do the connect |
| // |
| connection.connect(); |
| if (connection.getResponseCode() == 204) { |
| newStatus = connection |
| .getHeaderField(XACMLRestProperties.PROP_PDP_HTTP_HEADER_HB); |
| if (logger.isDebugEnabled()) { |
| logger |
| .debug("Heartbeat '" + pdp.getId() + "' status='" + newStatus + "'"); |
| } |
| } else { |
| // anything else is an unexpected result |
| newStatus = PDPStatus.Status.UNKNOWN.toString(); |
| logger.error("Heartbeat connect response code " |
| + connection.getResponseCode() + ": " + pdp.getId()); |
| } |
| } catch (UnknownHostException e) { |
| newStatus = PDPStatus.Status.NO_SUCH_HOST.toString(); |
| logger.error("Heartbeat '" + pdp.getId() + "' NO_SUCH_HOST"); |
| } catch (SocketTimeoutException e) { |
| newStatus = PDPStatus.Status.CANNOT_CONNECT.toString(); |
| logger.error("Heartbeat '" + pdp.getId() + "' connection timeout: " + e); |
| } catch (ConnectException e) { |
| newStatus = PDPStatus.Status.CANNOT_CONNECT.toString(); |
| logger.error("Heartbeat '" + pdp.getId() + "' cannot connect: " + e); |
| } catch (Exception e) { |
| newStatus = PDPStatus.Status.UNKNOWN.toString(); |
| logger.error("Heartbeat '" + pdp.getId() + "' connect exception: " + e, e); |
| } finally { |
| // cleanup the connection |
| connection.disconnect(); |
| } |
| |
| if (!pdp.getStatus().getStatus().toString().equals(newStatus)) { |
| if (logger.isDebugEnabled()) { |
| logger.debug("previous status='" + pdp.getStatus().getStatus() |
| + "' new Status='" + newStatus + "'"); |
| } |
| try { |
| setPDPSummaryStatus(pdp, newStatus); |
| } catch (PAPException e) { |
| logger.error("Unable to set state for PDP '" + pdp.getId() + "': " + e, e); |
| } |
| changeSeen = true; |
| } |
| |
| } |
| // |
| // Check for shutdown |
| // |
| if (!this.isRunning()) { |
| logger.info("isRunning is false, getting out of loop."); |
| break; |
| } |
| |
| // if any of the PDPs changed state, tell the ACs to update |
| if (changeSeen) { |
| notifyAC(); |
| } |
| |
| } |
| } catch (InterruptedException e) { |
| logger.error("Heartbeat interrupted. Shutting down"); |
| this.terminate(); |
| } |
| } |
| } |
| |
| // |
| // HELPER to change Group status when PDP status is changed |
| // |
| // (Must NOT be called from a method that is synchronized on the papEngine or it may deadlock) |
| // |
| |
| private void setPDPSummaryStatus(PDP pdp, PDPStatus.Status newStatus) throws PAPException { |
| setPDPSummaryStatus(pdp, newStatus.toString()); |
| } |
| |
| private void setPDPSummaryStatus(PDP pdp, String newStatus) throws PAPException { |
| synchronized (papEngine) { |
| StdPDPStatus status = (StdPDPStatus)pdp.getStatus(); |
| status.setStatus(PDPStatus.Status.valueOf(newStatus)); |
| ((StdPDP)pdp).setStatus(status); |
| |
| // now adjust the group |
| StdPDPGroup group = (StdPDPGroup)papEngine.getPDPGroup(pdp); |
| // if the PDP was just deleted it may transiently exist but not be in a group |
| if (group != null) { |
| group.resetStatus(); |
| } |
| } |
| } |
| |
| // |
| // Callback methods telling this servlet to notify PDPs of changes made by the PAP StdEngine |
| // in the PDP group directories |
| // |
| |
| @Override |
| public void changed() { |
| // all PDPs in all groups need to be updated/sync'd |
| Set<PDPGroup> groups; |
| try { |
| groups = papEngine.getPDPGroups(); |
| } catch (PAPException e) { |
| logger.error("getPDPGroups failed: " + e.getLocalizedMessage()); |
| throw new RuntimeException("Unable to get Groups: " + e); |
| } |
| for (PDPGroup group : groups) { |
| groupChanged(group); |
| } |
| } |
| |
| @Override |
| public void groupChanged(PDPGroup group) { |
| // all PDPs within one group need to be updated/sync'd |
| for (PDP pdp : group.getPdps()) { |
| pdpChanged(pdp); |
| } |
| } |
| |
| @Override |
| public void pdpChanged(PDP pdp) { |
| // kick off a thread to do an event notification for each PDP. |
| // This needs to be on a separate thread so that PDPs that do not respond (down, non-existent, etc) |
| // do not block the PSP response to the AC, which would freeze the GUI until all PDPs sequentially |
| // respond or time-out. |
| Thread t = new Thread(new UpdatePDPThread(pdp)); |
| t.start(); |
| } |
| |
| private class UpdatePDPThread implements Runnable { |
| private PDP pdp; |
| |
| // remember which PDP to notify |
| public UpdatePDPThread(PDP pdp) { |
| this.pdp = pdp; |
| } |
| |
| @Override |
| public void run() { |
| // send the current configuration to one PDP |
| HttpURLConnection connection = null; |
| try { |
| |
| // |
| // the Id of the PDP is its URL |
| // |
| if (logger.isDebugEnabled()) { |
| logger.debug("creating url for id '" + pdp.getId() + "'"); |
| } |
| // TODO - currently always send both policies and pips. Do we care enough to add code to allow |
| // sending just one or the other? |
| // TODO (need to change "cache=", implying getting some input saying which to change) |
| URL url = new URL(pdp.getId() + "?cache=all"); |
| |
| // |
| // Open up the connection |
| // |
| connection = (HttpURLConnection)url.openConnection(); |
| // |
| // Setup our method and headers |
| // |
| connection.setRequestMethod("PUT"); |
| // connection.setRequestProperty("Accept", "text/x-java-properties"); |
| connection.setRequestProperty("Content-Type", "text/x-java-properties"); |
| // connection.setUseCaches(false); |
| // |
| // Adding this in. It seems the HttpUrlConnection class does NOT |
| // properly forward our headers for POST re-direction. It does so |
| // for a GET re-direction. |
| // |
| // So we need to handle this ourselves. |
| // |
| // TODO - is this needed for a PUT? seems better to leave in for now? |
| // connection.setInstanceFollowRedirects(false); |
| // |
| // PLD - MUST be able to handle re-directs. |
| // |
| connection.setInstanceFollowRedirects(true); |
| connection.setDoOutput(true); |
| // connection.setDoInput(true); |
| try (OutputStream os = connection.getOutputStream()) { |
| |
| PDPGroup group = papEngine.getPDPGroup(pdp); |
| // if the PDP was just deleted, there is no group, but we want to send an update anyway |
| if (group == null) { |
| // create blank properties files |
| Properties policyProperties = new Properties(); |
| policyProperties.put(XACMLProperties.PROP_ROOTPOLICIES, ""); |
| policyProperties.put(XACMLProperties.PROP_REFERENCEDPOLICIES, ""); |
| policyProperties.store(os, ""); |
| |
| Properties pipProps = new Properties(); |
| pipProps.setProperty(XACMLProperties.PROP_PIP_ENGINES, ""); |
| pipProps.store(os, ""); |
| |
| } else { |
| // send properties from the current group |
| group.getPolicyProperties().store(os, ""); |
| Properties policyLocations = new Properties(); |
| for (PDPPolicy policy : group.getPolicies()) { |
| policyLocations.put(policy.getId() + ".url", XACMLPapServlet.papURL + "?id=" |
| + policy.getId()); |
| } |
| policyLocations.store(os, ""); |
| group.getPipConfigProperties().store(os, ""); |
| } |
| |
| } catch (Exception e) { |
| logger.error("Failed to send property file to " + pdp.getId(), e); |
| // Since this is a server-side error, it probably does not reflect a problem on the |
| // client, |
| // so do not change the PDP status. |
| return; |
| } |
| // |
| // Do the connect |
| // |
| connection.connect(); |
| if (connection.getResponseCode() == 204) { |
| logger.info("Success. We are configured correctly."); |
| setPDPSummaryStatus(pdp, PDPStatus.Status.UP_TO_DATE); |
| } else if (connection.getResponseCode() == 200) { |
| logger.info("Success. PDP needs to update its configuration."); |
| setPDPSummaryStatus(pdp, PDPStatus.Status.OUT_OF_SYNCH); |
| } else { |
| logger.warn("Failed: " + connection.getResponseCode() + " message: " |
| + connection.getResponseMessage()); |
| setPDPSummaryStatus(pdp, PDPStatus.Status.UNKNOWN); |
| } |
| } catch (Exception e) { |
| logger.error("Unable to sync config with PDP '" + pdp.getId() + "': " + e, e); |
| try { |
| setPDPSummaryStatus(pdp, PDPStatus.Status.UNKNOWN); |
| } catch (PAPException e1) { |
| logger.error("Unable to set status of PDP '" + pdp.getId() + "' to UNKNOWN: " + e, e); |
| } |
| } finally { |
| // cleanup the connection |
| connection.disconnect(); |
| |
| // tell the AC to update it's status info |
| notifyAC(); |
| } |
| |
| } |
| } |
| |
| // |
| // RESTful Interface from PAP to ACs notifying them of changes |
| // |
| |
| private void notifyAC() { |
| // kick off a thread to do one event notification for all registered ACs |
| // This needs to be on a separate thread so that ACs can make calls back to PAP to get the updated |
| // Group data |
| // as part of processing this message on their end. |
| Thread t = new Thread(new NotifyACThread()); |
| t.start(); |
| } |
| |
| private class NotifyACThread implements Runnable { |
| |
| @Override |
| public void run() { |
| List<String> disconnectedACs = new ArrayList<String>(); |
| // logger.debug("LIST SIZE="+adminConsoleURLStringList.size()); |
| |
| // There should be no Concurrent exception here because the list is a CopyOnWriteArrayList. |
| // The "for each" loop uses the collection's iterator under the covers, so it should be correct. |
| for (String acURL : adminConsoleURLStringList) { |
| HttpURLConnection connection = null; |
| try { |
| |
| acURL += "?PAPNotification=true"; |
| |
| // TODO - Currently we just tell AC that "Something changed" without being specific. Do we |
| // want to tell it which group/pdp changed? |
| // TODO - If so, put correct parameters into the Query string here |
| acURL += "&objectType=all" + "&action=update"; |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("creating url for id '" + acURL + "'"); |
| } |
| // TODO - currently always send both policies and pips. Do we care enough to add code to |
| // allow sending just one or the other? |
| // TODO (need to change "cache=", implying getting some input saying which to change) |
| |
| URL url = new URL(acURL); |
| |
| // |
| // Open up the connection |
| // |
| connection = (HttpURLConnection)url.openConnection(); |
| // |
| // Setup our method and headers |
| // |
| connection.setRequestMethod("PUT"); |
| connection.setRequestProperty("Content-Type", "text/x-java-properties"); |
| // |
| // Adding this in. It seems the HttpUrlConnection class does NOT |
| // properly forward our headers for POST re-direction. It does so |
| // for a GET re-direction. |
| // |
| // So we need to handle this ourselves. |
| // |
| // TODO - is this needed for a PUT? seems better to leave in for now? |
| connection.setInstanceFollowRedirects(false); |
| // |
| // Do not include any data in the PUT because this is just a |
| // notification to the AC. |
| // The AC will use GETs back to the PAP to get what it needs |
| // to fill in the screens. |
| // |
| |
| // |
| // Do the connect |
| // |
| connection.connect(); |
| if (connection.getResponseCode() == 204) { |
| logger.info("Success. We updated correctly."); |
| } else { |
| logger.warn("Failed: " + connection.getResponseCode() + " message: " |
| + connection.getResponseMessage()); |
| } |
| |
| } catch (Exception e) { |
| logger.error("Unable to sync config AC '" + acURL + "': " + e, e); |
| disconnectedACs.add(acURL); |
| } finally { |
| // cleanup the connection |
| connection.disconnect(); |
| } |
| } |
| |
| // remove any ACs that are no longer connected |
| if (disconnectedACs.size() > 0) { |
| adminConsoleURLStringList.removeAll(disconnectedACs); |
| } |
| |
| } |
| } |
| |
| } |