Commit 592d668a authored by cdanger's avatar cdanger

- Imported BasePdpExtensionRegistry,

ImmutableAttributeValueFactoryRegistry, and
StandardAttributeValueFactories from core-pdp-engine project
parent c9353f64
/**
* Copyright 2012-2018 Thales Services SAS.
*
* This file is part of AuthzForce CE.
*
* Licensed 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.
*/
package org.ow2.authzforce.core.pdp.api;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* This is a base implementation of <code>PdpExtensionRegistry</code>. This should be used as basis to implement (in a final class) an immutable PDP extension registry of a specific type. If you need
* a generic immutable PDP extension registry, see {
*
* @param <T>
* type of extension in this registry
* @version $Id: $
*/
public abstract class BasePdpExtensionRegistry<T extends PdpExtension> implements PdpExtensionRegistry<T>
{
private final Class<? super T> extClass;
private final Map<String, T> extensionsById;
private final transient String toString;
/**
* Instantiates immutable registry from a map.
*
* @param extensionClass
* extension class
* @param extensionsById
* extensions input map; the registry actually creates and uses an immutable copy of this map internally to avoid external modifications on the internal map
*/
protected BasePdpExtensionRegistry(final Class<? super T> extensionClass, final Map<String, ? extends T> extensionsById)
{
assert extensionClass != null && extensionsById != null;
this.extClass = extensionClass;
this.extensionsById = HashCollections.newImmutableMap(extensionsById);
this.toString = this + "( extensionClass= " + extClass.getCanonicalName() + " )";
}
/** {@inheritDoc} */
@Override
public final T getExtension(final String identity)
{
return extensionsById.get(identity);
}
/** {@inheritDoc} */
@Override
public final Set<T> getExtensions()
{
return HashCollections.newImmutableSet(extensionsById.values());
}
private static final class ExtensionToIdFunction<E extends PdpExtension> implements Function<E, String>
{
@Override
public String apply(final E extension) throws NullPointerException
{
assert extension != null;
return Preconditions.checkNotNull(extension, "One of the input extensions is invalid (null)").getId();
}
}
private static final Function<? extends PdpExtension, String> EXTENSION_TO_ID_FUNCTION = new ExtensionToIdFunction<>();
@SuppressWarnings("unchecked")
private static <E extends PdpExtension> Map<String, E> newImmutableMap(final Set<E> extensions)
{
return Maps.uniqueIndex(extensions, (Function<E, String>) EXTENSION_TO_ID_FUNCTION);
}
/**
* Instantiates immutable registry from a set of extensions
*
* @param extensionClass
* extension class (required not null)
* @param extensions
* extensions (required not null)
*/
protected BasePdpExtensionRegistry(final Class<? super T> extensionClass, final Set<? extends T> extensions)
{
this(extensionClass, newImmutableMap(extensions));
}
@Override
public String toString()
{
return toString;
}
}
/**
* Copyright 2012-2018 Thales Services SAS.
*
* This file is part of AuthzForce CE.
*
* Licensed 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.
*/
package org.ow2.authzforce.core.pdp.api.value;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import org.ow2.authzforce.core.pdp.api.AttributeSource;
import org.ow2.authzforce.core.pdp.api.AttributeSources;
import org.ow2.authzforce.core.pdp.api.BasePdpExtensionRegistry;
import org.ow2.authzforce.core.pdp.api.HashCollections;
import org.ow2.authzforce.core.pdp.api.expression.ConstantExpression;
import org.ow2.authzforce.core.pdp.api.expression.ConstantPrimitiveAttributeValueExpression;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import net.sf.saxon.s9api.XPathCompiler;
/**
* Immutable <code>AttributeValueFactoryRegistry</code>.
*
*
* @version $Id: $
*/
public final class ImmutableAttributeValueFactoryRegistry extends BasePdpExtensionRegistry<AttributeValueFactory<?>> implements AttributeValueFactoryRegistry
{
private static final IllegalArgumentException ILLEGAL_DATATYPE_ID_ARGUMENT_EXCEPTION = new IllegalArgumentException("Undefined datatype ID");
/**
* <p>
* createValue
* </p>
*
* @param attValFactory
* a {@link org.ow2.authzforce.core.pdp.api.value.AttributeValueFactory} object.
* @param content
* attribute value's mixed content.
* @param otherAttributes
* attribute value's optional XML attributes.
* @param xPathCompiler
* a {@link net.sf.saxon.s9api.XPathCompiler} object.
* @return a V object.
* @throws java.lang.IllegalArgumentException
* if any.
*/
private static <V extends AttributeValue> V newAttributeValue(final AttributeValueFactory<V> attValFactory, final List<Serializable> content, final Map<QName, String> otherAttributes,
final XPathCompiler xPathCompiler) throws IllegalArgumentException
{
assert attValFactory != null;
final V attrVal;
try
{
attrVal = attValFactory.getInstance(content, otherAttributes, xPathCompiler);
} catch (final IllegalArgumentException e)
{
throw new IllegalArgumentException("Invalid Attribute value for datatype '" + attValFactory.getDatatype() + "'", e);
}
return attrVal;
}
private static <V extends AttributeValue> ConstantExpression<V> newExpression(final AttributeValueFactory<V> attValFactory, final List<Serializable> content,
final Map<QName, String> otherAttributes, final XPathCompiler xPathCompiler) throws IllegalArgumentException
{
assert attValFactory != null;
final V rawValue = newAttributeValue(attValFactory, content, otherAttributes, xPathCompiler);
return new ConstantPrimitiveAttributeValueExpression<>(attValFactory.getDatatype(), rawValue);
}
/**
* Creates instance of immutable attribute bag from raw values, using {@link Bags#newAttributeBag(Datatype, Collection)} and {@code attributeValueFactory.getDatatype()} as datatype argument.
*
* @param attributeValueFactory
* factory in charge of create attribute values in the bag
*
* @param rawValues
* raw values to be parsed by {@code attributeValueFactory} to create {@link AttributeValue}s
*
* @return attribute bag
* @throws IllegalArgumentException
* if {@code attributeValueFactory == null } or {@code rawValues} has at least one element which is null:
* {@code rawValues != null && !rawValues.isEmpty() && rawValues.iterator().next() == null}
*/
private static <AV extends AttributeValue> AttributeBag<AV> newAttributeBag(final StringParseableValue.Factory<AV> attributeValueFactory, final Collection<? extends Serializable> rawValues,
final AttributeSource attValSrc) throws IllegalArgumentException
{
assert attributeValueFactory != null;
final Datatype<AV> elementDatatype = attributeValueFactory.getDatatype();
if (rawValues == null || rawValues.isEmpty())
{
return Bags.emptyAttributeBag(elementDatatype, null, attValSrc);
}
return Bags.newAttributeBag(elementDatatype, rawValues.stream().map(rawValue -> attributeValueFactory.getInstance(rawValue)).collect(Collectors.toList()), attValSrc);
}
private final Map<Class<? extends Serializable>, StringParseableValue.Factory<?>> inputClassToAttValFactory;
private final Set<Entry<Class<? extends Serializable>, StringParseableValue.Factory<?>>> nonFinalInputClassToAttValFactory;
/**
* <p>
* Constructor for BaseDatatypeFactoryRegistry.
* </p>
*
* @param attributeValueFactories
* attribute value factories
*/
public ImmutableAttributeValueFactoryRegistry(final Collection<? extends AttributeValueFactory<?>> attributeValueFactories)
{
super(AttributeValueFactory.class,
HashCollections.newImmutableSet(Preconditions.checkNotNull(attributeValueFactories, "Input attribute datatype factories undefined (attributeValueFactories == null)")));
final Map<Class<? extends Serializable>, StringParseableValue.Factory<?>> mutableJavaClassToAttValFactory = HashCollections.newUpdatableMap();
attributeValueFactories.forEach(factory -> {
if (factory instanceof StringParseableValue.Factory)
{
final StringParseableValue.Factory<?> simpleValueFactory = (StringParseableValue.Factory<?>) factory;
simpleValueFactory.getSupportedInputTypes().forEach(inputType -> mutableJavaClassToAttValFactory.putIfAbsent(inputType, simpleValueFactory));
}
});
inputClassToAttValFactory = HashCollections.newImmutableMap(mutableJavaClassToAttValFactory);
/*
* Using inputClassToAttValFactory.get(instanceClass) to get the corresponding factory is faster that doing many instanceOf checks but only works for equal match. For non-final classes, we
* still have to do the instanceOf check because the instance class might not be equal (i.e same class) but a subclass. So we gather the non-final classes for which instanceOf check is
* necessary iff no equal match.
*/
final Set<Entry<Class<? extends Serializable>, StringParseableValue.Factory<?>>> mutableSet = inputClassToAttValFactory.entrySet().stream()
.filter(e -> !Modifier.isFinal(e.getKey().getModifiers())).collect(Collectors.toSet());// HashCollections.newUpdatableSet(JAVA_TYPE_TO_ATT_VALUE_FACTORY.size());
nonFinalInputClassToAttValFactory = ImmutableSet.copyOf(mutableSet);
}
/** {@inheritDoc} */
@Override
public ConstantExpression<? extends AttributeValue> newExpression(final String datatypeId, final List<Serializable> content, final Map<QName, String> otherAttributes,
final XPathCompiler xPathCompiler) throws IllegalArgumentException
{
if (datatypeId == null)
{
throw ILLEGAL_DATATYPE_ID_ARGUMENT_EXCEPTION;
}
final AttributeValueFactory<?> datatypeFactory = getExtension(datatypeId);
if (datatypeFactory == null)
{
throw new IllegalArgumentException("Unsupported datatype ID (no supporting attribute value factory/parser): " + datatypeId);
}
return newExpression(datatypeFactory, content, otherAttributes, xPathCompiler);
}
@Override
public StringParseableValue.Factory<?> getCompatibleFactory(final Class<? extends Serializable> rawValueClass)
{
final StringParseableValue.Factory<?> attValFactoryFromMap = inputClassToAttValFactory.get(rawValueClass);
if (attValFactoryFromMap == null)
{
/*
* This may look like the collection is fully filtered before findfirst() is called but it is not the case. "All intermediate operations e.g. filter(), map() etc are lazy and they are only
* executed when a terminal operation like findFirst() or forEach() is called.
*
* This also means, a lot of opportunity for optimization depending upon the size of the original list." (Quote from:
* http://javarevisited.blogspot.fr/2016/03/how-to-find-first-element-of-stream-in.html)
*/
final Optional<Entry<Class<? extends Serializable>, StringParseableValue.Factory<?>>> optionalResult = nonFinalInputClassToAttValFactory.stream()
.filter(e -> e.getKey().isAssignableFrom(rawValueClass)).findFirst();
if (optionalResult.isPresent())
{
return optionalResult.get().getValue();
}
throw new UnsupportedOperationException("Unsupported input value type: '" + rawValueClass + "' (no suitable XACML datatype factory found)");
}
return attValFactoryFromMap;
}
@Override
public AttributeValue newAttributeValue(final Serializable rawValue) throws IllegalArgumentException, UnsupportedOperationException
{
Preconditions.checkArgument(rawValue != null, "Null input value");
final StringParseableValue.Factory<?> factory = getCompatibleFactory(rawValue.getClass());
if (factory == null)
{
throw new UnsupportedOperationException("Unsupported input value type: '" + rawValue.getClass() + "' (no suitable XACML datatype factory found)");
}
return factory.getInstance(rawValue);
}
@Override
public AttributeBag<?> newAttributeBag(final Collection<? extends Serializable> rawVals, AttributeSource attributeValueSource) throws UnsupportedOperationException, IllegalArgumentException
{
Preconditions.checkArgument(rawVals != null && !rawVals.isEmpty(), "Null/empty list of input values");
final Serializable rawVal0 = rawVals.iterator().next();
Preconditions.checkArgument(rawVal0 != null, "One of the input values (#0) is null");
final StringParseableValue.Factory<?> factory = getCompatibleFactory(rawVal0.getClass());
if (factory == null)
{
throw new UnsupportedOperationException("Unsupported input value type: '" + rawVal0.getClass() + "' (no suitable XACML datatype factory found)");
}
return newAttributeBag(factory, rawVals, attributeValueSource);
}
@Override
public AttributeBag<?> newAttributeBag(final Collection<? extends Serializable> rawVals) throws UnsupportedOperationException, IllegalArgumentException
{
return newAttributeBag(rawVals, AttributeSources.REQUEST);
}
}
/**
* Copyright 2012-2018 Thales Services SAS.
*
* This file is part of AuthzForce CE.
*
* Licensed 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.
*/
package org.ow2.authzforce.core.pdp.api.value;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.security.auth.x500.X500Principal;
import javax.xml.bind.DatatypeConverter;
import javax.xml.namespace.QName;
import org.ow2.authzforce.core.pdp.api.HashCollections;
import org.ow2.authzforce.core.pdp.api.PdpExtensionRegistry.PdpExtensionComparator;
import org.ow2.authzforce.core.pdp.api.XmlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import net.sf.saxon.s9api.XPathCompiler;
/**
* XACML standard datatypes
*
*
* @version $Id: $
*/
public final class StandardAttributeValueFactories
{
private static final Logger LOGGER = LoggerFactory.getLogger(StandardAttributeValueFactories.class);
/**
* string
*/
public static final StringContentOnlyValueFactory<StringValue> STRING = new StringContentOnlyValueFactory<StringValue>(StandardDatatypes.STRING)
{
@Override
public StringValue parse(final String val)
{
return StringValue.parse(val);
}
};
private static final Set<Class<? extends Serializable>> SUPPORTED_BOOLEAN_FACTORY_INPUT_TYPES = HashCollections.newImmutableSet(Arrays.asList(Boolean.class, String.class));
/**
* boolean
*/
public static final StringParseableValue.Factory<BooleanValue> BOOLEAN = new StringParseableValue.Factory<BooleanValue>(StandardDatatypes.BOOLEAN)
{
@Override
public Set<Class<? extends Serializable>> getSupportedInputTypes()
{
return SUPPORTED_BOOLEAN_FACTORY_INPUT_TYPES;
}
@Override
public BooleanValue parse(final String val)
{
return BooleanValue.getInstance(val);
}
@Override
public BooleanValue getInstance(final Serializable value)
{
if (value instanceof Boolean)
{
return new BooleanValue((Boolean) value);
}
if (value instanceof String)
{
return parse((String) value);
}
throw newInvalidInputTypeException(value);
}
};
private static final Set<Class<? extends Serializable>> SUPPORTED_INTEGER_FACTORY_INPUT_TYPES = HashCollections
.newImmutableSet(Arrays.asList(Short.class, Integer.class, Long.class, BigInteger.class, String.class));
private static abstract class IntegerValueFactory extends StringParseableValue.Factory<IntegerValue>
{
private IntegerValueFactory()
{
super(StandardDatatypes.INTEGER);
}
@Override
public Set<Class<? extends Serializable>> getSupportedInputTypes()
{
return SUPPORTED_INTEGER_FACTORY_INPUT_TYPES;
}
}
/**
* integer parsed into {@link Integer}, therefore supports medium-size integers (representing xsd:int)
*/
public static final StringParseableValue.Factory<IntegerValue> MEDIUM_INTEGER = new IntegerValueFactory()
{
@Override
public IntegerValue parse(final String val) throws IllegalArgumentException
{
final int i;
try
{
i = DatatypeConverter.parseInt(val);
} catch (final NumberFormatException e)
{
throw new IllegalArgumentException(this + ": input value not valid or too big for Java int: " + val);
}
return IntegerValue.valueOf(i);
}
@Override
public IntegerValue getInstance(final Serializable value) throws IllegalArgumentException
{
if (value instanceof Short)
{
return IntegerValue.valueOf(((Short) value).intValue());
}
if (value instanceof Integer)
{
return IntegerValue.valueOf(((Integer) value).intValue());
}
if (value instanceof Long)
{
final Long l = (Long) value;
final int i;
try
{
i = Math.toIntExact(l.longValue());
} catch (final ArithmeticException e)
{
throw new IllegalArgumentException(this + ": input value not supported (too big for Java int): " + value);
}
return IntegerValue.valueOf(i);
}
if (value instanceof BigInteger)
{
final BigInteger bigInt = (BigInteger) value;
final int i;
try
{
i = bigInt.intValueExact();
} catch (final ArithmeticException e)
{
throw new IllegalArgumentException(this + ": input value not supported (too big for Java int): " + value);
}
return IntegerValue.valueOf(i);
}
if (value instanceof String)
{
return parse((String) value);
}
throw newInvalidInputTypeException(value);
}
};
private static IntegerValue longOrSmallerIntToIntegerValue(final Serializable value)
{
if (value instanceof Short)
{
return IntegerValue.valueOf(((Short) value).intValue());
}
if (value instanceof Integer)
{
return IntegerValue.valueOf(((Integer) value).intValue());
}
if (value instanceof Long)
{
return IntegerValue.valueOf(((Long) value).longValue());
}
return null;
}
/**
* integer parsed into {@link Long}, therefore supports long integers (representing xsd:long)
*/
public static final StringParseableValue.Factory<IntegerValue> LONG_INTEGER = new IntegerValueFactory()
{
@Override
public IntegerValue parse(final String val) throws IllegalArgumentException
{
final long i;
try
{
i = DatatypeConverter.parseLong(val);
} catch (final NumberFormatException e)
{
throw new IllegalArgumentException(this + ": input value not valid or too big for Java long: " + val);
}
return IntegerValue.valueOf(i);
}
@Override
public IntegerValue getInstance(final Serializable value) throws IllegalArgumentException
{
final IntegerValue smallIntegerValue = longOrSmallerIntToIntegerValue(value);
if (smallIntegerValue != null)
{
return smallIntegerValue;
}
if (value instanceof BigInteger)
{
final BigInteger bigInt = (BigInteger) value;
final long i;
try
{
i = bigInt.longValueExact();
} catch (final ArithmeticException e)
{
throw new IllegalArgumentException(this + ": input value not supported (too big for Java long): " + bigInt);
}
return IntegerValue.valueOf(i);
}
if (value instanceof String)
{
return parse((String) value);
}
throw newInvalidInputTypeException(value);
}
};
/**
* integer parsed into {@link BigInteger}, therefore supports arbitrary-precision integers (i.e. any xsd:integer)
*/
public static final StringParseableValue.Factory<IntegerValue> BIG_INTEGER = new IntegerValueFactory()
{
private IntegerValue getInstance(final BigInteger bigi)
{
final long i;
try
{
i = bigi.longValueExact();
return IntegerValue.valueOf(i);
} catch (final ArithmeticException e)
{
LOGGER.debug("Input integer too big to fit in a long: {}", bigi);
}
return new IntegerValue(new ArbitrarilyBigInteger(bigi));
}
@Override
public IntegerValue parse(final String val) throws IllegalArgumentException
{
final BigInteger bigInt;
try
{
bigInt = DatatypeConverter.parseInteger(val);
} catch (final NumberFormatException e)
{
throw new IllegalArgumentException(this + ": input value not valid: " + val);
}
return getInstance(bigInt);
}
@Override
public IntegerValue getInstance(final Serializable value) throws IllegalArgumentException
{
final IntegerValue smallIntegerValue = longOrSmallerIntToIntegerValue(value);
if (smallIntegerValue != null)
{
return smallIntegerValue;
}
if (value instanceof BigInteger)
{
final BigInteger bigInt = (BigInteger) value;
return getInstance(bigInt);
}