Skip to content

Commit

Permalink
Ability for stricter schema checking for provisioning, better AD tests
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Jul 6, 2017
1 parent 5f2d8a5 commit a717be8
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 20 deletions.
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2017 Evolveum
*
* 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 com.evolveum.midpoint.common.refinery;

import javax.xml.namespace.QName;


/**
* @author semancik
*
*/
class RefinedObjectClassDefinitionKey {
private QName typeName;
private String intent;

public RefinedObjectClassDefinitionKey(RefinedObjectClassDefinition rObjectClassDefinition) {
typeName = rObjectClassDefinition.getTypeName();
intent = rObjectClassDefinition.getIntent();
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((intent == null) ? 0 : intent.hashCode());
result = prime * result + ((typeName == null) ? 0 : typeName.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
RefinedObjectClassDefinitionKey other = (RefinedObjectClassDefinitionKey) obj;
if (intent == null) {
if (other.intent != null) {
return false;
}
} else if (!intent.equals(other.intent)) {
return false;
}
if (typeName == null) {
if (other.typeName != null) {
return false;
}
} else if (!typeName.equals(other.typeName)) {
return false;
}
return true;
}

@Override
public String toString() {
return "(type=" + typeName + ", intent=" + intent + ")";
}
}
Expand Up @@ -49,7 +49,7 @@ public class RefinedResourceSchemaImpl implements RefinedResourceSchema {

// Original resource schema is there to make parsing easier.
// But it is also useful in some cases, e.g. we do not need to pass both refined schema and
// original schema as a metod parameter.
// original schema as a method parameter.
private ResourceSchema originalResourceSchema;

// This object contains the real data of the refined schema
Expand All @@ -60,6 +60,12 @@ private RefinedResourceSchemaImpl(@NotNull ResourceSchema originalResourceSchema
this.resourceSchema = new ResourceSchemaImpl(originalResourceSchema.getNamespace(), originalResourceSchema.getPrismContext());
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public Collection<ObjectClassComplexTypeDefinition> getObjectClassDefinitions() {
return (Collection) getRefinedDefinitions();
}

@Override
public List<? extends RefinedObjectClassDefinition> getRefinedDefinitions() {
return resourceSchema.getDefinitions(RefinedObjectClassDefinition.class);
Expand Down Expand Up @@ -580,4 +586,21 @@ public <TD extends TypeDefinition> Collection<? extends TD> findTypeDefinitionsB

//endregion

public static void validateRefinedSchema(RefinedResourceSchema refinedSchema, PrismObject<ResourceType> resource) throws SchemaException {

Set<RefinedObjectClassDefinitionKey> discrs = new HashSet<>();

for (RefinedObjectClassDefinition rObjectClassDefinition: refinedSchema.getRefinedDefinitions()) {
QName typeName = rObjectClassDefinition.getTypeName();
RefinedObjectClassDefinitionKey key = new RefinedObjectClassDefinitionKey(rObjectClassDefinition);
if (discrs.contains(key)) {
throw new SchemaException("Duplicate definition of object class "+key+" in resource schema of "+resource);
}
discrs.add(key);

ResourceTypeUtil.validateObjectClassDefinition(rObjectClassDefinition, resource);
}
}


}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2016 Evolveum
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -70,4 +70,7 @@ default boolean isAnyType() {
@NotNull
@Override
PrismPropertyDefinition<T> clone();

@Override
Class<T> getTypeClass();
}
Expand Up @@ -327,8 +327,46 @@ public static boolean canConvert(Class<?> clazz) {
public static boolean canConvert(QName xsdType) {
return (XsdTypeMapper.getXsdToJavaMapping(xsdType) != null);
}

public static boolean isMatchingType(Class<?> expectedClass, Class<?> actualClass) {
if (expectedClass.isAssignableFrom(actualClass)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, int.class, Integer.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, long.class, Long.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, boolean.class, Boolean.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, byte.class, Byte.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, char.class, Character.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, float.class, Float.class)) {
return true;
}
if (isMatchingType(expectedClass, actualClass, double.class, Double.class)) {
return true;
}
return false;
}

private static boolean isMatchingType(Class<?> expectedClass, Class<?> actualClass, Class<?> lowerClass, Class<?> upperClass) {
if (lowerClass.isAssignableFrom(expectedClass) && upperClass.isAssignableFrom(actualClass)) {
return true;
}
if (lowerClass.isAssignableFrom(actualClass) && upperClass.isAssignableFrom(expectedClass)) {
return true;
}
return false;
}

public static <T> T convertValueElementAsScalar(Element valueElement, Class<T> type) throws SchemaException {
public static <T> T convertValueElementAsScalar(Element valueElement, Class<T> type) throws SchemaException {
return toJavaValue(valueElement, type);
}

Expand Down
Expand Up @@ -29,13 +29,17 @@
import org.jetbrains.annotations.Nullable;
import org.w3c.dom.Element;

import com.evolveum.midpoint.prism.Definition;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.schema.CapabilityUtil;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceSchema;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
Expand Down Expand Up @@ -648,4 +652,47 @@ public static ShadowCheckType getShadowConstaintsCheck(ResourceType resource) {
}
return shadowCheckType;
}

public static boolean isValidateSchema(ResourceType resource) {
ResourceConsistencyType consistency = resource.getConsistency();
if (consistency == null) {
return false;
}
return Boolean.TRUE.equals(consistency.isValidateSchema());
}

// TODO: maybe later move to ResourceSchema?
public static void validateSchema(ResourceSchema resourceSchema, PrismObject<ResourceType> resource) throws SchemaException {

Set<QName> objectClassNames = new HashSet<>();

for (ObjectClassComplexTypeDefinition objectClassDefinition: resourceSchema.getObjectClassDefinitions()) {
QName typeName = objectClassDefinition.getTypeName();
if (objectClassNames.contains(typeName)) {
throw new SchemaException("Duplicate definition of object class "+typeName+" in resource schema of "+resource);
}
objectClassNames.add(typeName);

validateObjectClassDefinition(objectClassDefinition, resource);
}
}

public static void validateObjectClassDefinition(ObjectClassComplexTypeDefinition objectClassDefinition,
PrismObject<ResourceType> resource) throws SchemaException {
Set<QName> attributeNames = new HashSet<>();
for (ResourceAttributeDefinition<?> attributeDefinition: objectClassDefinition.getAttributeDefinitions()) {
QName attrName = attributeDefinition.getName();
if (attributeNames.contains(attrName)) {
throw new SchemaException("Duplicate definition of attribute "+attrName+" in object class "+objectClassDefinition.getTypeName()+" in resource schema of "+resource);
}
attributeNames.add(attrName);
}

Collection<? extends ResourceAttributeDefinition<?>> primaryIdentifiers = objectClassDefinition.getPrimaryIdentifiers();
Collection<? extends ResourceAttributeDefinition<?>> secondaryIdentifiers = objectClassDefinition.getSecondaryIdentifiers();

if (primaryIdentifiers.isEmpty() && secondaryIdentifiers.isEmpty()) {
throw new SchemaException("No identifiers in definition of object class "+objectClassDefinition.getTypeName()+" in resource schema of "+resource);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010-2016 Evolveum
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,13 +20,16 @@
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.*;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;

Expand All @@ -46,6 +49,8 @@
*/
public class ShadowUtil {

private static final Trace LOGGER = TraceManager.getTrace(ShadowUtil.class);

public static Collection<ResourceAttribute<?>> getPrimaryIdentifiers(ShadowType shadowType) {
return getPrimaryIdentifiers(shadowType.asPrismObject());
}
Expand Down Expand Up @@ -710,4 +715,45 @@ public static ResourceAttribute<?> fixAttributePath(ResourceAttribute<?> attribu
return fixedAttribute;
}

// TODO: may be useful to move to ObjectClassComplexTypeDefinition later?
public static void validateAttributeSchema(PrismObject<ShadowType> shadow,
ObjectClassComplexTypeDefinition objectClassDefinition) throws SchemaException {
ResourceAttributeContainer attributesContainer = getAttributesContainer(shadow);
for (ResourceAttribute<?> attribute: attributesContainer.getAttributes()) {
validateAttribute(attribute, objectClassDefinition);
}
}

// TODO: may be useful to move to ResourceAttributeDefinition later?
private static <T> void validateAttribute(ResourceAttribute<T> attribute,
ObjectClassComplexTypeDefinition objectClassDefinition) throws SchemaException {
QName attrName = attribute.getElementName();
ResourceAttributeDefinition<T> attrDef = objectClassDefinition.findAttributeDefinition(attrName);
if (attrDef == null) {
throw new SchemaException("No definition for attribute "+attrName+" in object class "+objectClassDefinition);
}
List<PrismPropertyValue<T>> pvals = attribute.getValues();
if (pvals == null || pvals.isEmpty()) {
if (attrDef.isMandatory()) {
throw new SchemaException("Mandatory attribute "+attrName+" has no value");
} else {
return;
}
}
if (pvals.size() > 1 && attrDef.isSingleValue()) {
throw new SchemaException("Single-value attribute "+attrName+" has "+pvals.size()+" values");
}
Class<T> expectedClass = attrDef.getTypeClass();
for (PrismPropertyValue<T> pval: pvals) {
T val = pval.getValue();
if (val == null) {
throw new SchemaException("Null value in attribute "+attrName);
}
LOGGER.info("MMMMMMMMMMMM: {}:{}\n {} <-> {}", attrName, attrDef, expectedClass, val.getClass());
if (!XmlTypeConverter.isMatchingType(expectedClass, val.getClass())) {
throw new SchemaException("Wrong value in attribute "+attrName+"; expected class "+attrDef.getTypeClass().getSimpleName()+", but was "+val.getClass());
}
}
}

}
Expand Up @@ -4432,6 +4432,22 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="validateSchema" type="xsd:boolean" minOccurs="0" default="false">
<xsd:annotation>
<xsd:documentation>
If set to true then midPoint will validate the schema of objects that leave
and enter midPoint by the connectors. MidPoint will checks if datatypes and
multiplicity is correct and issue hard errors in any mismatch.

EXPERIMENTAL: this feature is used mostly for internal checks during
development and testing. It is not intended for production use.
Those checks may slow the system down and they may also may impact
robustness. MidPoint is usually able to deal data types that are slightly
wrong and auto-correct them. Enabling this feature will cause that an error
is thrown even if cases that may get auto-corrected.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="caseIgnoreAttributeNames" type="xsd:boolean" minOccurs="0" default="false">
<xsd:annotation>
<xsd:documentation>
Expand Down
Expand Up @@ -667,7 +667,11 @@ private ResourceSchema fetchResourceSchema(PrismObject<ResourceType> resource, M
List<QName> generateObjectClasses = ResourceTypeUtil.getSchemaGenerationConstraints(resource);
ConnectorInstance connectorInstance = connectorManager.getConfiguredConnectorInstance(connectorSpec, false, parentResult);
LOGGER.trace("Trying to get schema from {}", connectorSpec);
return connectorInstance.fetchResourceSchema(generateObjectClasses, parentResult);
ResourceSchema resourceSchema = connectorInstance.fetchResourceSchema(generateObjectClasses, parentResult);
if (ResourceTypeUtil.isValidateSchema(resource.asObjectable())) {
ResourceTypeUtil.validateSchema(resourceSchema, resource);
}
return resourceSchema;

}

Expand Down
Expand Up @@ -621,6 +621,11 @@ public String addShadow(PrismObject<ShadowType> shadow, OperationProvisioningScr
}

private void preAddChecks(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, SecurityViolationException {
checkConstraints(ctx, shadow, task, result);
validateSchema(ctx, shadow, task, result);
}

private void checkConstraints(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, SecurityViolationException {
ShadowCheckType shadowConstraintsCheck = ResourceTypeUtil.getShadowConstaintsCheck(ctx.getResource());
if (shadowConstraintsCheck == ShadowCheckType.NONE) {
return;
Expand All @@ -645,6 +650,12 @@ private void preAddChecks(ProvisioningContext ctx, PrismObject<ShadowType> shado
throw new ObjectAlreadyExistsException("Conflicting shadow already exists on "+ctx.getResource());
}
}

private void validateSchema(ProvisioningContext ctx, PrismObject<ShadowType> shadow, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, SecurityViolationException {
if (ResourceTypeUtil.isValidateSchema(ctx.getResource())) {
ShadowUtil.validateAttributeSchema(shadow, ctx.getObjectClassDefinition());
}
}

private ResourceOperationDescription createSuccessOperationDescription(ProvisioningContext ctx,
PrismObject<ShadowType> shadowType, ObjectDelta delta, OperationResult parentResult)
Expand Down

0 comments on commit a717be8

Please sign in to comment.