blob: 53d3a1293c1268d62c257f37f887d27c5d61fa40 [file] [log] [blame]
/*
* 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) 2014 AT&T Knowledge Ventures
* Unpublished and Not for Publication
* All Rights Reserved
*/
package org.apache.openaz.xacml.std.pip.engines.ldap;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.NamingException;
import javax.naming.directory.SearchResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.openaz.xacml.api.Attribute;
import org.apache.openaz.xacml.api.AttributeValue;
import org.apache.openaz.xacml.api.DataType;
import org.apache.openaz.xacml.api.DataTypeException;
import org.apache.openaz.xacml.api.DataTypeFactory;
import org.apache.openaz.xacml.api.pip.PIPEngine;
import org.apache.openaz.xacml.api.pip.PIPException;
import org.apache.openaz.xacml.api.pip.PIPFinder;
import org.apache.openaz.xacml.api.pip.PIPRequest;
import org.apache.openaz.xacml.api.pip.PIPResponse;
import org.apache.openaz.xacml.std.StdAttribute;
import org.apache.openaz.xacml.std.datatypes.DataTypes;
import org.apache.openaz.xacml.std.pip.StdPIPRequest;
import org.apache.openaz.xacml.std.pip.engines.Configurables;
import org.apache.openaz.xacml.util.FactoryException;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
public class ConfigurableLDAPResolver implements LDAPResolver {
private static DataTypeFactory dataTypeFactory = null;
static {
try {
dataTypeFactory = DataTypeFactory.newInstance();
} catch (FactoryException fx) {
throw new RuntimeException(fx);
}
Velocity.setProperty("runtime.log.logsystem.log4j.logger", "MAIN_LOG");
Velocity.init();
}
private Log logger = LogFactory.getLog(this.getClass());
private String defaultIssuer;
private String id;
private String base;
private String filter;
private Map<String, PIPRequest> baseParameters;
private Map<String, PIPRequest> filterParameters;
private Map<String, PIPRequest> filterView;
public ConfigurableLDAPResolver() {
}
@Override
public void configure(String id, Properties properties, String defaultIssuer) throws PIPException {
/*
* Save these values
*/
this.id = id;
this.defaultIssuer = defaultIssuer;
this.base = properties.getProperty(id + ".base");
this.filter = properties.getProperty(id + ".filter");
Set<String> baseParametersNames = prepareVelocityTemplate(this.base);
Set<String> filterParametersNames = prepareVelocityTemplate(this.filter);
this.baseParameters = Configurables.getPIPRequestMap(id + ".base", "parameters", properties, null);
this.filterParameters = Configurables
.getPIPRequestMap(id + ".filter", "parameters", properties, null);
// make sure we have all required parameters
if (!this.baseParameters.keySet().containsAll(baseParametersNames)) {
throw new PIPException(
"The 'base' template contains parameters that were not specified in its map.");
}
if (!this.filterParameters.keySet().containsAll(filterParametersNames)) {
throw new PIPException(
"The 'filter' template contains parameters that were not specified in its map.");
}
this.filterView = Configurables.getPIPRequestMap(id + ".filter", "view", properties, defaultIssuer);
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "\nbase '" + this.base + "', parameters "
+ this.baseParameters + "\nfilter '" + this.filter + "', parameters "
+ this.filterParameters + ", view " + this.filterView);
}
}
public void store(String id, Properties properties) throws PIPException {
properties.setProperty(id + ".base", this.base);
properties.setProperty(id + ".filter", this.filter);
Configurables.setPIPRequestMap(this.baseParameters, id + ".base", "parameters", properties);
Configurables.setPIPRequestMap(this.filterParameters, id + ".filter", "parameters", properties);
Configurables.setPIPRequestMap(this.filterView, id + ".filter", "view", properties);
}
/*
* @return the set of parameters names required by the given velocity template
*/
private Set<String> prepareVelocityTemplate(String template) throws PIPException {
VelocityContext vctx = new VelocityContext();
EventCartridge vec = new EventCartridge();
VelocityParameterReader reader = new VelocityParameterReader();
vec.addEventHandler(reader);
vec.attachToContext(vctx);
try {
Velocity.evaluate(vctx, new StringWriter(), "LdapResolver", template);
} catch (ParseErrorException pex) {
throw new PIPException("Velocity template preparation failed", pex);
} catch (MethodInvocationException mix) {
throw new PIPException("Velocity template preparation failed", mix);
} catch (ResourceNotFoundException rnfx) {
throw new PIPException("Velocity template preparation failed", rnfx);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + template + " with parameters " + reader.parameters);
}
return reader.parameters;
}
private String evaluateVelocityTemplate(String template,
final Map<String, PIPRequest> templateParameters,
final PIPEngine pipEngine, final PIPFinder pipFinder)
throws PIPException {
StringWriter out = new StringWriter();
VelocityContext vctx = new VelocityContext();
EventCartridge vec = new EventCartridge();
VelocityParameterWriter writer = new VelocityParameterWriter(pipEngine, pipFinder, templateParameters);
vec.addEventHandler(writer);
vec.attachToContext(vctx);
try {
Velocity.evaluate(vctx, out, "LdapResolver", template);
} catch (ParseErrorException pex) {
throw new PIPException("Velocity template evaluation failed", pex);
} catch (MethodInvocationException mix) {
throw new PIPException("Velocity template evaluation failed", mix);
} catch (ResourceNotFoundException rnfx) {
throw new PIPException("Velocity template evaluation failed", rnfx);
}
this.logger.warn("(" + id + ") " + " template yields " + out.toString());
return out.toString();
}
private Object evaluatePIPRequest(PIPRequest pipRequest, PIPFinder pipFinder)
throws PIPException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + pipRequest);
}
PIPResponse pipResponse = pipFinder.getMatchingAttributes(pipRequest, null);
if (pipResponse.getStatus() == null || pipResponse.getStatus().isOk()) {
Collection<Attribute> listAttributes = pipResponse.getAttributes();
if (listAttributes.size() > 0) {
if (listAttributes.size() > 1) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "PIPFinder returned more than one Attribute for "
+ pipRequest);
}
throw new PIPException("PIPFinder returned more than one Attribute for "
+ pipRequest.toString());
}
Collection<AttributeValue<?>> listAttributeValuesReturned = listAttributes.iterator().next()
.getValues();
if (listAttributeValuesReturned.size() > 0) {
if (listAttributeValuesReturned.size() > 1) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") "
+ "PIPFinder returned more than one AttributeValue for "
+ pipRequest);
}
return null;
}
AttributeValue<?> attributeValue = listAttributeValuesReturned.iterator().next();
// this is to hoping the string representation of the value is accurate
try {
return DataTypes.DT_STRING.convert(attributeValue.getValue());
} catch (DataTypeException dtx) {
throw new PIPException("Fauiled to extract attribute value", dtx);
}
}
}
}
return null;
}
@Override
public String getBase(PIPEngine pipEngine, PIPRequest pipRequest, PIPFinder pipFinder)
throws PIPException {
if (!filterView.containsValue(pipRequest)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + pipRequest + " not in " + filterView);
}
return null;
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + pipRequest);
}
return evaluateVelocityTemplate(this.base, this.baseParameters, pipEngine, pipFinder);
}
public void setBase(String base) throws PIPException {
Set<String> baseParametersNames = prepareVelocityTemplate(base);
// make sure we have all required parameters
if (!this.baseParameters.keySet().containsAll(baseParametersNames)) {
throw new PIPException(
"The 'base' template contains parameters that were not specified in its map.");
}
this.base = base;
}
@Override
public String getFilterString(PIPEngine pipEngine, PIPRequest pipRequest, PIPFinder pipFinder)
throws PIPException {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + pipRequest);
}
if (!filterView.containsValue(pipRequest)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "request " + pipRequest + " not in " + filterView);
}
return null;
}
return evaluateVelocityTemplate(this.filter, this.filterParameters, pipEngine, pipFinder);
}
public void setFilterString(String filter) throws PIPException {
Set<String> filterParametersNames = prepareVelocityTemplate(filter);
// make sure we have all required parameters
if (!this.filterParameters.keySet().containsAll(filterParametersNames)) {
throw new PIPException(
"The 'filter' template contains parameters that were not specified in its map.");
}
this.filter = filter;
}
private Attribute decodeResultValue(SearchResult searchResult, String view, PIPRequest viewRequest) {
AttributeValue<?> attributeValue = null;
Collection<AttributeValue<?>> attributeMultiValue = null;
DataType<?> dataType = null;
this.logger.warn("(" + id + ") " + "SearchResult attributes: " + searchResult.getAttributes());
try {
dataType = dataTypeFactory.getDataType(viewRequest.getDataTypeId());
if (dataType == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "Unknown data type in " + viewRequest);
}
return null;
}
if ("dn".equalsIgnoreCase(view)) {
attributeValue = dataType.createAttributeValue(searchResult.getNameInNamespace());
} else {
javax.naming.directory.Attribute dirAttr = searchResult.getAttributes().get(view);
if (dirAttr != null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "directory attribute '" + view + "' value is '"
+ dirAttr + "'");
}
// we could guide this more elaborately by object class ..
if (dirAttr.size() == 1) {
attributeValue = dataType.createAttributeValue(dirAttr.get().toString());
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace("(" + id + ") " + "SearchResult yields a multi-valued '" + view
+ "'");
}
attributeMultiValue = new HashSet<AttributeValue<?>>();
// we should
for (int i = 0; i < dirAttr.size(); i++) {
attributeMultiValue.add(dataType.createAttributeValue(dirAttr.get().toString()));
}
}
} else {
this.logger.warn("(" + id + ") " + "SearchResult did not provide a value for '" + view
+ "'");
return null;
}
}
} catch (DataTypeException dtx) {
this.logger.error("(" + id + ") " + "Failed to decode search result", dtx);
return null;
} catch (NamingException nx) {
this.logger.error("(" + id + ") " + "Failed to decode search result", nx);
return null;
}
Attribute attr = null;
if (attributeMultiValue == null) {
attr = new StdAttribute(viewRequest.getCategory(), viewRequest.getAttributeId(), attributeValue,
viewRequest.getIssuer(), false);
} else {
attr = new StdAttribute(viewRequest.getCategory(), viewRequest.getAttributeId(),
attributeMultiValue, viewRequest.getIssuer(), false);
}
this.logger.warn("(" + id + ") " + " providing attribute " + attr);
return attr;
}
@Override
public List<Attribute> decodeResult(SearchResult searchResult) throws PIPException {
List<Attribute> attributes = new ArrayList<Attribute>();
for (Map.Entry<String, PIPRequest> viewEntry : this.filterView.entrySet()) {
Attribute attribute = this.decodeResultValue(searchResult, viewEntry.getKey(),
viewEntry.getValue());
if (attribute != null) {
attributes.add(attribute);
}
}
return attributes;
}
private class VelocityParameterHandler implements ReferenceInsertionEventHandler {
/* velocity parameter pattern: we're just trying to extract the name */
private Pattern vpp = Pattern.compile("\\{(\\w)+\\}");
@Override
public Object referenceInsert(String theReference, Object theValue) {
/*
* unfortunately Velocity does not give us simply the variable name but it's whole template
* representation, i.e. ${var_name} or derivatives. We look for whatever is between { and }
*/
Matcher vvm = vpp.matcher(theReference);
String param = null;
// Check all occurance
if (vvm.find()) {
String vv = vvm.group();
param = vv.substring(1, vv.length() - 1);
} else {
// variable name pattern not right?
param = "";
}
if (ConfigurableLDAPResolver.this.logger.isTraceEnabled()) {
ConfigurableLDAPResolver.this.logger.trace("(" + id + ") " + "Velocity parameter: " + param);
}
return param;
}
}
/* */
private class VelocityParameterReader extends VelocityParameterHandler {
private Set<String> parameters = new HashSet<String>();
@Override
public Object referenceInsert(String theReference, Object theValue) {
String param = (String)super.referenceInsert(theReference, theValue);
parameters.add(param);
return "";
}
}
private class VelocityParameterWriter extends VelocityParameterHandler {
private PIPEngine engine;
private PIPFinder finder;
private Map<String, PIPRequest> parameters;
public VelocityParameterWriter(PIPEngine engine, PIPFinder finder, Map<String, PIPRequest> parameters) {
this.engine = engine;
this.finder = finder;
this.parameters = parameters;
}
@Override
public Object referenceInsert(String theReference, Object theValue) {
String param = (String)super.referenceInsert(theReference, theValue);
try {
PIPRequest request = parameters.get(param);
if (ConfigurableLDAPResolver.this.logger.isTraceEnabled()) {
ConfigurableLDAPResolver.this.logger.trace("(" + id + ") " + "Velocity parameter: "
+ param + " requests " + request);
}
if (null == request)
throw new RuntimeException("Parameter '" + param + "' is not available");
Object val = ConfigurableLDAPResolver.this.evaluatePIPRequest(request, this.finder);
if (null != val) {
return val;
} else {
if (param.startsWith("_")) {
return "*";
} else {
return null;
}
}
} catch (PIPException pipx) {
throw new RuntimeException(pipx);
}
}
}
@Override
public void attributesRequired(Collection<PIPRequest> attributes) {
for (String key : this.filterView.keySet()) {
attributes.add(new StdPIPRequest(this.filterView.get(key)));
}
}
@Override
public void attributesProvided(Collection<PIPRequest> attributes) {
for (String key : this.filterParameters.keySet()) {
PIPRequest attribute = this.filterParameters.get(key);
attributes.add(new StdPIPRequest(attribute.getCategory(), attribute.getAttributeId(), attribute
.getDataTypeId(),
(attribute.getIssuer() != null
? attribute.getIssuer() : this.defaultIssuer)));
}
}
}