Skip to content

Commit

Permalink
Fill-in association identifiers completely
Browse files Browse the repository at this point in the history
The provisioning module fill-is in shadowRef to all association values
when returning a shadowed resource object. However, identifiers were
originally not touched - they may or may not be present completely,
depending on the situation. This caused issues where the naming
attribute was expected to evaluate tolerant/intolerant patterns
but only primary identifier (uid) was present.

Now we copy all the identifiers from the resolved shadow, along with
the shadowRef. It should ensure that tolerant/intolerant patterns
can be always evaluated.

Related to MID-8815.

(cherry picked and adapted from commit
b5db18c)
  • Loading branch information
mederly committed Apr 24, 2023
1 parent 7ae32c7 commit d4139f2
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@
import java.util.stream.Collectors;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.ComplexTypeDefinition;
import com.evolveum.midpoint.prism.*;

import com.google.common.annotations.VisibleForTesting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.LocalItemDefinitionStore;
import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.schema.constants.MidPointConstants;
import com.evolveum.midpoint.util.MiscUtil;
Expand Down Expand Up @@ -157,4 +155,27 @@ default Collection<? extends QName> getNamesOfAttributesWithInboundExpressions()
.map(ItemDefinition::getItemName)
.collect(Collectors.toCollection(HashSet::new));
}

/**
* Converts a {@link PrismProperty} into corresponding {@link ResourceAttribute}.
* Used in the process of "definition application" in `applyDefinitions` and similar methods.
*/
default <T> @NotNull ResourceAttribute<T> propertyToAttribute(PrismProperty<T> property)
throws SchemaException {
QName attributeName = property.getElementName();
//noinspection unchecked
ResourceAttributeDefinition<T> attributeDefinition =
(ResourceAttributeDefinition<T>) findAttributeDefinition(attributeName);
if (attributeDefinition == null) {
throw new SchemaException("No definition for attribute " + attributeName + " in " + this);
}
ResourceAttribute<T> attribute = new ResourceAttributeImpl<>(attributeName, attributeDefinition);
for (PrismPropertyValue<T> pval : property.getValues()) {
// MID-5833 This is manual copy process, could we could assume original property is correctly constructed
attribute.addIgnoringEquivalents(pval.clone());
}
attribute.applyDefinition(attributeDefinition);
attribute.setIncomplete(property.isIncomplete());
return attribute;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType;

Expand All @@ -34,20 +34,8 @@ static ResourceAttributeContainer convertFromContainer(PrismContainer<?> origAtt
ResourceAttributeContainer attributesContainer = createEmptyContainer(elementName, resourceObjectDefinition);
for (Item item: origAttrContainer.getValue().getItems()) {
if (item instanceof PrismProperty) {
PrismProperty<?> property = (PrismProperty)item;
QName attributeName = property.getElementName();
ResourceAttributeDefinition attributeDefinition = resourceObjectDefinition.findAttributeDefinition(attributeName);
if (attributeDefinition == null) {
throw new SchemaException("No definition for attribute "+attributeName+" in object class "+resourceObjectDefinition);
}
ResourceAttribute attribute = new ResourceAttributeImpl(attributeName, attributeDefinition);
for(PrismPropertyValue pval: property.getValues()) {
// MID-5833 This is manual copy process, could we could assume original property is correctly constructed
attribute.addIgnoringEquivalents(pval.clone());
}
attributesContainer.add(attribute);
attribute.applyDefinition(attributeDefinition);
attribute.setIncomplete(item.isIncomplete());
attributesContainer.add(
resourceObjectDefinition.propertyToAttribute((PrismProperty<?>) item));
} else {
throw new SchemaException("Cannot process item of type "+item.getClass().getSimpleName()+", attributes can only be properties");
}
Expand All @@ -67,6 +55,16 @@ static ResourceAttributeContainerImpl createEmptyContainer(QName elementName,
@Override
ResourceAttributeContainerDefinition getDefinition();

default @NotNull ResourceObjectDefinition getResourceObjectDefinitionRequired() {
ResourceAttributeContainerDefinition definition =
MiscUtil.stateNonNull(
getDefinition(),
() -> "No definition in " + this);
return MiscUtil.stateNonNull(
definition.getComplexTypeDefinition(),
() -> "No resource object definition in " + definition);
}

/**
* TODO review docs
*
Expand All @@ -82,6 +80,13 @@ static ResourceAttributeContainerImpl createEmptyContainer(QName elementName,

void add(ResourceAttribute<?> attribute) throws SchemaException;

/**
* Adds a {@link PrismProperty}, converting to {@link ResourceAttribute} if needed.
*
* Requires the resource object definition (i.e. complex type definition) be present.
*/
void addAdoptedIfNeeded(@NotNull PrismProperty<?> attribute) throws SchemaException;

/**
* Returns a (single) primary identifier.
*
Expand Down Expand Up @@ -283,6 +288,10 @@ Collection<ResourceAttribute<?>> extractAttributesByDefinitions(
*/
<X> ResourceAttribute<X> findAttribute(QName attributeQName);

default boolean containsAttribute(QName attributeName) {
return findAttribute(attributeName) != null;
}

/**
* Finds a specific attribute in the resource object by definition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ public void add(ResourceAttribute<?> attribute) throws SchemaException {
super.add(attribute);
}

@Override
public void addAdoptedIfNeeded(@NotNull PrismProperty<?> property) throws SchemaException {
ResourceAttribute<?> attribute;
if (property instanceof ResourceAttribute<?>) {
attribute = (ResourceAttribute<?>) property;
} else {
attribute = getResourceObjectDefinitionRequired().propertyToAttribute(property);
}
add(attribute);
}

@Override
public PrismProperty<?> getPrimaryIdentifier() {
Collection<ResourceAttribute<?>> attrDefs = getPrimaryIdentifiers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ public class MidPointTestConstants {
public static final ItemName QNAME_ENTRY_UUID = new ItemName(NS_RI, "entryUUID");
public static final ItemPath PATH_ENTRY_UUID = ItemPath.create(ShadowType.F_ATTRIBUTES, QNAME_ENTRY_UUID);
public static final ItemName QNAME_CAR_LICENSE = new ItemName(MidPointConstants.NS_RI, "carLicense");
public static final ItemName QNAME_EMPLOYEE_TYPE = new ItemName(MidPointConstants.NS_RI, "employeeType");}
public static final ItemName QNAME_EMPLOYEE_TYPE = new ItemName(MidPointConstants.NS_RI, "employeeType");
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ public boolean isWildcard() {
*
* The returned context is based on "refined" resource type definition.
*/
public ProvisioningContext spawnForKindIntent(
@NotNull public ProvisioningContext spawnForKindIntent(
@NotNull ShadowKindType kind,
@NotNull String intent)
throws SchemaException, ConfigurationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private ScopedDefinition createScopedDefinitionForBulkOperation(ResourceOperatio
*
* "Unknown" kind/intent is not supported.
*/
ProvisioningContext spawnForKindIntent(
@NotNull ProvisioningContext spawnForKindIntent(
@NotNull ProvisioningContext originalCtx,
@NotNull ShadowKindType kind,
@NotNull String intent) throws SchemaException, ConfigurationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
import java.util.Iterator;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.schema.processor.ResourceObjectDefinition;
import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.schema.processor.*;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.evolveum.midpoint.schema.processor.ResourceAssociationDefinition;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.polystring.PolyString;
Expand All @@ -30,8 +30,6 @@
import com.evolveum.midpoint.provisioning.impl.ProvisioningContext;
import com.evolveum.midpoint.provisioning.impl.resourceobjects.ResourceObjectConverter;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.schema.processor.ResourceAttribute;
import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.util.exception.*;
Expand Down Expand Up @@ -170,13 +168,13 @@ private void copyAndAdoptAssociations(OperationResult result) throws SchemaExcep
return;
}

LOGGER.trace("Start adopting associations: {}", resourceObjectAssociations.size());
LOGGER.trace("Start adopting associations: {} value(s)", resourceObjectAssociations.size());

PrismContainer<ShadowAssociationType> associationsCloned = resourceObjectAssociations.clone();
resultingShadowedObject.addReplaceExisting(associationsCloned);
Iterator<PrismContainerValue<ShadowAssociationType>> associationIterator = associationsCloned.getValues().iterator();
while (associationIterator.hasNext()) {
if (!setAssociationValueShadowRef(associationIterator.next(), result)) {
if (!adoptAssociationValue(associationIterator.next(), result)) {
associationIterator.remove();
}
}
Expand Down Expand Up @@ -330,16 +328,20 @@ private Collection<QName> getAuxiliaryObjectClasses() throws SchemaException {
/**
* Tries to acquire (find/create) shadow for given association value and fill-in its reference.
*
* Also, provides all identifier values from the shadow (if it's classified). Normally, the name is present among
* identifiers, and it is sufficient. However, there may be cases when UID is there only. But the name is sometimes needed,
* e.g. when evaluating tolerant/intolerant patterns on associations. See also MID-8815.
*
* @return false if the association value does not fit and should be removed
*/
private boolean setAssociationValueShadowRef(PrismContainerValue<ShadowAssociationType> associationValue, OperationResult result)
private boolean adoptAssociationValue(PrismContainerValue<ShadowAssociationType> associationValue, OperationResult result)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
ExpressionEvaluationException, SecurityViolationException, EncryptionException {

LOGGER.trace("Determining shadowRef for {}", associationValue);

ResourceAttributeContainer identifierContainer = ShadowUtil.getAttributesContainer(associationValue,
ShadowAssociationType.F_IDENTIFIERS);
ResourceAttributeContainer identifierContainer =
ShadowUtil.getAttributesContainer(associationValue, ShadowAssociationType.F_IDENTIFIERS);

ShadowAssociationType associationValueBean = associationValue.asContainerable();
QName associationName = associationValueBean.getName();
Expand All @@ -358,6 +360,11 @@ private boolean setAssociationValueShadowRef(PrismContainerValue<ShadowAssociati
if (doesAssociationMatch(rAssociationDef, entitlementRepoShadow)) {
LOGGER.trace("Association value matches. Repo shadow is: {}", entitlementRepoShadow);
associationValueBean.setShadowRef(createObjectRef(entitlementRepoShadow, beans.prismContext));
if (ShadowUtil.isClassified(entitlementRepoShadow.asObjectable())) {
addMissingIdentifiers(identifierContainer, ctxEntitlement, entitlementRepoShadow.asObjectable());
} else {
// We are not sure we have the right shadow. Hence let us be careful and not copy any identifiers.
}
} else {
LOGGER.trace("Association value does not match. Repo shadow is: {}", entitlementRepoShadow);
// We have association value that does not match its definition. This may happen because the association attribute
Expand All @@ -371,6 +378,25 @@ private boolean setAssociationValueShadowRef(PrismContainerValue<ShadowAssociati
return true;
}

/** Copies missing identifiers from entitlement repo shadow to the association value; does not overwrite anything! */
private void addMissingIdentifiers(
ResourceAttributeContainer identifiersContainer,
ProvisioningContext ctxEntitlement,
ShadowType shadow)
throws SchemaException {
var identifierDefinitions = ctxEntitlement.getObjectDefinitionRequired().getAllIdentifiers();
for (ResourceAttributeDefinition<?> identifierDef : identifierDefinitions) {
ItemName identifierName = identifierDef.getItemName();
if (!identifiersContainer.containsAttribute(identifierName)) {
var shadowIdentifier =
shadow.asPrismObject().findProperty(ShadowType.F_ATTRIBUTES.append(identifierName));
if (shadowIdentifier != null) {
identifiersContainer.addAdoptedIfNeeded(shadowIdentifier);
}
}
}
}

@Nullable
private PrismObject<ShadowType> acquireEntitlementRepoShadow(PrismContainerValue<ShadowAssociationType> associationValue,
ResourceAttributeContainer identifierContainer, ProvisioningContext ctxEntitlement, OperationResult result)
Expand Down Expand Up @@ -411,7 +437,7 @@ private PrismObject<ShadowType> acquireEntitlementRepoShadow(PrismContainerValue
} catch (SchemaException e) {
// The entitlement to which we point is bad. Simply ignore this association value.
result.muteLastSubresultError();
LOGGER.warn("The entitlement identified by {} referenced from {} violates the schema. Skipping. Original error: {}-{}",
LOGGER.warn("The entitlement identified by {} referenced from {} violates the schema. Skipping. Original error: {}",
associationValue, resourceObject, e.getMessage(), e);
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import static com.evolveum.midpoint.prism.util.PrismTestUtil.serializeToXml;
import static com.evolveum.midpoint.schema.constants.MidPointConstants.NS_RI;

import static com.evolveum.midpoint.test.util.MidPointTestConstants.QNAME_DN;

import static com.evolveum.midpoint.test.util.MidPointTestConstants.QNAME_ENTRY_UUID;

import static org.assertj.core.api.Assertions.*;
import static org.testng.AssertJUnit.*;

Expand Down Expand Up @@ -2260,13 +2264,19 @@ public void test402AddAccountMorganWithAssociation() throws Exception {
assertRepoShadow(ACCOUNT_MORGAN_OID)
.assertName(ACCOUNT_MORGAN_DN);

ShadowType swashbucklersShadow = getShadowRepo(GROUP_SWASHBUCKLERS_OID).asObjectable();

// @formatter:off
ShadowAsserter<Void> provisioningShadowAsserter = assertShadowProvisioning(ACCOUNT_MORGAN_OID)
.assertName(ACCOUNT_MORGAN_DN)
.associations()
.assertSize(1)
.association(ASSOCIATION_GROUP_NAME)
.assertShadowOids(GROUP_SWASHBUCKLERS_OID)
.assertSize(1)
.forShadowOid(GROUP_SWASHBUCKLERS_OID)
.assertIdentifierValueMatching(QNAME_DN, GROUP_SWASHBUCKLERS_DN)
.assertIdentifierValueMatching(QNAME_ENTRY_UUID, swashbucklersShadow.getPrimaryIdentifierValue())
.end()
.end()
.end();
// @formatter:on
Expand Down Expand Up @@ -2511,7 +2521,7 @@ public void test419PhantomRenameMorgan() throws Exception {
OperationResult result = task.getResult();

ObjectDelta<ShadowType> delta = deltaFor(ShadowType.class)
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(MidPointTestConstants.QNAME_DN))
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(QNAME_DN))
.replace("uid=morgan,ou=People,dc=example,dc=com")
.asObjectDelta(ACCOUNT_MORGAN_OID);

Expand Down Expand Up @@ -2545,14 +2555,14 @@ public void test420PhantomRenameMorganRotten() throws Exception {
OperationResult result = task.getResult();

ObjectDelta<ShadowType> rotDelta = deltaFor(ShadowType.class)
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(MidPointTestConstants.QNAME_DN))
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(QNAME_DN))
.replace("uid=morgan-rotten,ou=People,dc=example,dc=com")
.asObjectDelta(ACCOUNT_MORGAN_OID);
repositoryService.modifyObject(ShadowType.class, ACCOUNT_MORGAN_OID, rotDelta.getModifications(), result);

// This is no-op on resource. (The DN on resource has not changed.)
ObjectDelta<ShadowType> delta = deltaFor(ShadowType.class)
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(MidPointTestConstants.QNAME_DN))
.item(MidPointTestConstants.PATH_DN, getAccountAttributeDefinitionRequired(QNAME_DN))
.replace("uid=morgan,ou=People,dc=example,dc=com")
.asObjectDelta(ACCOUNT_MORGAN_OID);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@
<shortcutValueAttribute>ri:dn</shortcutValueAttribute>
<explicitReferentialIntegrity>true</explicitReferentialIntegrity>
</association>
<protected>
<filter>
<q:equal>
<q:matching>http://prism.evolveum.com/xml/ns/public/matching-rule-3#distinguishedName</q:matching>
<protected>
<filter>
<q:equal>
<q:matching>http://prism.evolveum.com/xml/ns/public/matching-rule-3#distinguishedName</q:matching>
<q:path>attributes/ri:dn</q:path>
<!-- WilDCapiTaLIzaTioN and spacing makes sure that this is matched properly -->
<q:value>uid=cAlyPSo, ou=PeoPle, DC=example,dc=COM</q:value>
Expand Down

0 comments on commit d4139f2

Please sign in to comment.