/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.security.claims.attributequery.common; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.xml.namespace.QName; import javax.xml.transform.stream.StreamSource; import javax.xml.ws.Dispatch; import javax.xml.ws.Service; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.cxf.interceptor.LoggingInInterceptor; import org.apache.cxf.interceptor.LoggingOutInterceptor; import org.apache.cxf.jaxws.DispatchImpl; import org.apache.cxf.resource.URIResolver; import org.apache.cxf.rt.security.claims.ClaimCollection; import org.apache.cxf.sts.claims.ClaimsHandler; import org.apache.cxf.sts.claims.ClaimsParameters; import org.apache.cxf.sts.claims.ProcessedClaim; import org.apache.cxf.sts.claims.ProcessedClaimCollection; import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.AttributeStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import ddf.security.PropertiesLoader; import ddf.security.samlp.SimpleSign; public class AttributeQueryClaimsHandler implements ClaimsHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AttributeQueryClaimsHandler.class); protected static final String ERROR_RETRIEVING_ATTRIBUTES = "Error retrieving attributes from external attribute store [{}] for DN [{}]. "; private Object signatureProperties; private Object encryptionProperties; private String wsdlLocation; private String serviceName; private String portName; private String attributeMapLocation; private List<String> supportedClaims; private Map<String, String> attributeMap; protected SimpleSign simpleSign; protected String externalAttributeStoreUrl; protected String issuer; protected String destination; public AttributeQueryClaimsHandler() { LOGGER.debug("Creating {}", this.getClass()); } /** * Gets the supported claim types. * * @return List of supported claims. */ @Override public List<URI> getSupportedClaimTypes() { LOGGER.debug("Getting supported claim types."); List<URI> supportedClaimTypes = new ArrayList<>(); try { for (String claim : supportedClaims) { supportedClaimTypes.add(new URI(claim)); } } catch (URISyntaxException e) { LOGGER.info("Not a valid URI for claim type {}.", e); } return supportedClaimTypes; } /** * Retrieves claims from the external attribute store. * * @param claims The collection of claims. * @return The collection of claims or an empty collection if there are no security claims. * @throws URISyntaxException */ @Override public ProcessedClaimCollection retrieveClaimValues(ClaimCollection claims, ClaimsParameters parameters) { ProcessedClaimCollection claimCollection = new ProcessedClaimCollection(); Principal principal = parameters.getPrincipal(); if (principal == null) { return claimCollection; } String nameId = getNameId(principal); try { if (!StringUtils.isEmpty(nameId)) { ProcessedClaimCollection securityClaimCollection = getAttributes(nameId); // If security claim collection came back empty, return an empty claim collection. if (!CollectionUtils.isEmpty(securityClaimCollection)) { claimCollection.addAll(securityClaimCollection); } } } catch (URISyntaxException e) { LOGGER.info( ERROR_RETRIEVING_ATTRIBUTES + "Set log level to DEBUG for more information.", externalAttributeStoreUrl, nameId); LOGGER.debug(ERROR_RETRIEVING_ATTRIBUTES, externalAttributeStoreUrl, nameId, e); } return claimCollection; } /** * Retrieve the name from the principal. * * @param principal of the user. * @return CN of the user. */ protected String getNameId(Principal principal) { return principal.getName(); } /** * Gets the attributes for the supplied user from the external attribute store. * Returns null if the AttributeQueryClient is null. * * @param nameId used for the request. * @return The collection of attributes retrieved from the external attribute store. * @throws URISyntaxException */ protected ProcessedClaimCollection getAttributes(String nameId) throws URISyntaxException { ProcessedClaimCollection claimCollection = new ProcessedClaimCollection(); LOGGER.debug("Sending AttributeQuery Request."); AttributeQueryClient attributeQueryClient; Assertion assertion; try { attributeQueryClient = createAttributeQueryClient(simpleSign, externalAttributeStoreUrl, issuer, destination); if (attributeQueryClient == null) { return null; } assertion = attributeQueryClient.query(nameId); if (assertion != null) { createClaims(claimCollection, assertion); } } catch (AttributeQueryException ex) { LOGGER.info("Error occurred in AttributeQueryClient, did not retrieve response. Set log level for \"org.codice.ddf.security.claims.attributequery.common\" to DEBUG for more information."); LOGGER.debug("Error occurred in AttributeQueryClient, did not retrieve response.", ex); } return claimCollection; } /** * Creates claims from the extracted attributes. * * @param claimsCollection The collection of claims. * @param assertion Assertion from the response. * @return The collection of claims. * @throws URISyntaxException */ protected ProcessedClaimCollection createClaims(ProcessedClaimCollection claimsCollection, Assertion assertion) throws URISyntaxException { // Should only contain one Attribute Statement. AttributeStatement attributeStatement = assertion.getAttributeStatements() .get(0); List<Attribute> attributeList = attributeStatement.getAttributes(); // If the claim is supported, check if it has an attribute mapping. // If so, map the attribute (attribute value -> mapped attribute value) // and create the claim, otherwise, create the claim using its original attribute value. for (Attribute attribute : attributeList) { for (String claimType : supportedClaims) { if (claimType.equalsIgnoreCase(attribute.getName())) { String claimValue = attribute.getDOM() .getTextContent(); if (attributeMap.containsKey(claimValue)) { claimsCollection.add(createSingleValuedClaim(claimType, attributeMap.get(claimValue))); } else { claimsCollection.add(createSingleValuedClaim(claimType, claimValue)); } break; } } } return claimsCollection; } /** * Creates a single valued claim. * * @param claimType The claim type. * @param claimValue The claim value. * @return The claim. * @throws URISyntaxException */ protected ProcessedClaim createSingleValuedClaim(String claimType, String claimValue) throws URISyntaxException { ProcessedClaim claim = new ProcessedClaim(); claim.setClaimType(new URI(claimType)); claim.setValues(ImmutableList.<Object>of(claimValue)); LOGGER.debug("Created claim with type [{}] and value [{}].", claimType, claimValue); return claim; } /** * Creates a client to interface with an external attribute store via an AttributeQuery request. * * @param simpleSign to create signature for request * @param externalAttributeStoreUrl endpoint of external web service * @param issuer of request * @param destination of request * @return AttributeQueryClient */ protected AttributeQueryClient createAttributeQueryClient(SimpleSign simpleSign, String externalAttributeStoreUrl, String issuer, String destination) { Dispatch<StreamSource> dispatcher = createDispatcher(createService()); if (dispatcher == null) { return null; } return new AttributeQueryClient(dispatcher, simpleSign, externalAttributeStoreUrl, issuer, destination); } /** * Creates a dynamic service from the provided wsdl location. */ protected Service createService() { Service service = null; URL wsdlURL; if (StringUtils.isNotBlank(wsdlLocation) && StringUtils.isNotBlank(serviceName)) { try { URIResolver uriResolver = new URIResolver(); uriResolver.resolve("", wsdlLocation, this.getClass()); wsdlURL = uriResolver.isResolved() ? uriResolver.getURL() : new URL(wsdlLocation); service = Service.create(wsdlURL, QName.valueOf(serviceName)); } catch (Exception e) { LOGGER.info("Unable to create service from WSDL location. Set log level for \"org.codice.ddf.security.claims.attributequery.common\" to DEBUG for more information."); LOGGER.debug("Unable to create service from WSDL location.", e); } } return service; } /** * Creates a dispatcher for dispatching requests. */ protected Dispatch<StreamSource> createDispatcher(Service service) { Dispatch<StreamSource> dispatch = null; if (service != null) { dispatch = service.createDispatch(QName.valueOf(portName), StreamSource.class, Service.Mode.MESSAGE); dispatch.getRequestContext() .put(Dispatch.ENDPOINT_ADDRESS_PROPERTY, externalAttributeStoreUrl); dispatch.getRequestContext() .put("ws-security.signature.properties", signatureProperties); dispatch.getRequestContext() .put("ws-security.encryption.properties", encryptionProperties); ((DispatchImpl) dispatch).getClient() .getBus() .getOutInterceptors() .add(new LoggingInInterceptor()); ((DispatchImpl) dispatch).getClient() .getBus() .getOutInterceptors() .add(new LoggingOutInterceptor()); } return dispatch; } public void setSignatureProperties(Object signatureProperties) { this.signatureProperties = signatureProperties; } public void setEncryptionProperties(Object encryptionProperties) { this.encryptionProperties = encryptionProperties; } public void setWsdlLocation(String wsdlLocation) { this.wsdlLocation = wsdlLocation; } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public void setPortName(String portName) { this.portName = portName; } public void setSimpleSign(SimpleSign simpleSign) { this.simpleSign = simpleSign; } public void setExternalAttributeStoreUrl(String externalAttributeStoreUrl) { this.externalAttributeStoreUrl = externalAttributeStoreUrl; } public void setIssuer(String issuer) { this.issuer = issuer; } public void setDestination(String destination) { this.destination = destination; } public void setAttributeMapLocation(String attributeMapLocation) { if (StringUtils.isNotBlank(attributeMapLocation) && !attributeMapLocation.equals(this.attributeMapLocation)) { attributeMap = PropertiesLoader.toMap(PropertiesLoader.loadProperties( attributeMapLocation)); this.attributeMapLocation = attributeMapLocation; } } public void setSupportedClaims(List<String> supportedClaims) { this.supportedClaims = supportedClaims; } }