Skip to content

Commit

Permalink
Add candidate confidence limit for ID Match
Browse files Browse the repository at this point in the history
Also, checkCandidateOwner was fixed for potential matches.
  • Loading branch information
mederly committed Aug 16, 2022
1 parent c6aceb0 commit 3e0af32
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -662,12 +662,58 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="correlationProperties" type="tns:IdMatchCorrelationPropertiesType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
What properties should be sent to the ID Match service. Default: All pre-focus properties.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.6</a:since>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="candidateConfidenceLimit" type="xsd:double" minOccurs="0" default="0.9">
<xsd:annotation>
<xsd:documentation>
Maximum confidence value that is acceptable for the given candidate.
If the ID Match service provides a higher value, it is cropped to this one.
(The same is true if ID Match provides no value.)
</xsd:documentation>
<xsd:appinfo>
<a:since>4.6</a:since>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="idMatchCorrelator" type="tns:IdMatchCorrelatorType"/>

<xsd:complexType name="IdMatchCorrelationPropertiesType">
<xsd:annotation>
<xsd:documentation>
What properties should be sent to the ID Match service.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.6</a:since>
<a:container>true</a:container>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="path" type="t:ItemPathType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Path of the property. It may start with $shadow (or $projection) - then it denotes
the value from the shadow being correlated.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:long"/>
</xsd:complexType>

<xsd:element name="composition" type="tns:CorrelatorCompositionDefinitionType">
<xsd:annotation>
<xsd:documentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Objects;

import com.evolveum.midpoint.model.api.correlation.CorrelationContext;
import com.evolveum.midpoint.util.MiscUtil;
Expand Down Expand Up @@ -45,6 +46,8 @@
*/
class IdMatchCorrelator extends BaseCorrelator<IdMatchCorrelatorType> {

private static final double DEFAULT_CONFIDENCE_LIMIT = 0.9;

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

/** Service that resolves "reference ID" for resource objects being correlated. */
Expand Down Expand Up @@ -81,15 +84,6 @@ class IdMatchCorrelator extends BaseCorrelator<IdMatchCorrelatorType> {
.correlate(result);
}

@Override
protected @NotNull CorrelationExplanation explainInternal(
@NotNull CorrelationContext correlationContext,
@NotNull FocusType candidateOwner,
@NotNull OperationResult result) {
// Values for potential matches are not correct
return new CorrelationExplanation.UnsupportedCorrelationExplanation(correlatorContext.getConfiguration());
}

@Override
protected double checkCandidateOwnerInternal(
@NotNull CorrelationContext correlationContext,
Expand Down Expand Up @@ -162,11 +156,18 @@ public CorrelationResult correlate(OperationResult result)
throws SchemaException, CommunicationException, SecurityViolationException, ConfigurationException,
ExpressionEvaluationException, ObjectNotFoundException {
MatchingResult mResult = executeMatchAndStoreTheResult(result);
String referenceId = mResult.getReferenceId();
if (referenceId != null) {
return checkCandidateOwnerByReferenceId(candidateOwner, referenceId, result);
String definiteReferenceId = mResult.getReferenceId();
if (definiteReferenceId != null) {
return checkCandidateOwnerByReferenceId(candidateOwner, definiteReferenceId, result) ? 1 : 0;
} else {
// FIXME - here we should check the potential matches obtained from ID Match service!
for (PotentialMatch potentialMatch : mResult.getPotentialMatches()) {
String referenceId = potentialMatch.getReferenceId();
if (referenceId != null) {
if (checkCandidateOwnerByReferenceId(candidateOwner, referenceId, result)) {
return getConfidence(potentialMatch);
}
}
}
return 0;
}
}
Expand All @@ -184,7 +185,7 @@ private CorrelationResult correlateUsingKnownReferenceId(String referenceId, Ope
var focus = findFocusWithGivenReferenceId(referenceId, result);
if (focus != null) {
// Note that ID Match does not provide confidence values for certain matches
// And we don't support custom confidence values here.
// And we don't support custom confidence values here. Hence always 1.0.
return CorrelationResult.of(
CandidateOwnersMap.from(
List.of(new CandidateOwner(focus, referenceId, 1.0))));
Expand Down Expand Up @@ -266,7 +267,7 @@ private CorrelationContext createContextWithReferenceId(String referenceId) {
return clonedContext;
}

private double checkCandidateOwnerByReferenceId(FocusType candidateOwner, String referenceId, OperationResult result)
private boolean checkCandidateOwnerByReferenceId(FocusType candidateOwner, String referenceId, OperationResult result)
throws ConfigurationException, SchemaException, ExpressionEvaluationException, CommunicationException,
SecurityViolationException, ObjectNotFoundException {
ReferenceIdResolutionConfig referenceIdResolutionConfig = new ReferenceIdResolutionConfig(correlationContext);
Expand All @@ -279,17 +280,18 @@ private double checkCandidateOwnerByReferenceId(FocusType candidateOwner, String
}
}

private double checkCandidateOwnerDirectly(FocusType candidateOwner, String referenceId, ItemPath referenceIdPath) {
private boolean checkCandidateOwnerDirectly(FocusType candidateOwner, String referenceId, ItemPath referenceIdPath) {
Object candidateReferenceId = candidateOwner.asPrismObject().getPropertyRealValue(referenceIdPath, Object.class);
return candidateReferenceId != null && candidateReferenceId.toString().equals(referenceId) ? 1 : 0;
return candidateReferenceId != null && candidateReferenceId.toString().equals(referenceId);
}

private double checkCandidateOwnerUsingCorrelator(
private boolean checkCandidateOwnerUsingCorrelator(
FocusType candidateOwner, CorrelatorConfiguration followOnConfiguration, OperationResult result)
throws ConfigurationException, SchemaException, ExpressionEvaluationException, CommunicationException,
SecurityViolationException, ObjectNotFoundException {
return instantiateChild(followOnConfiguration, task, result)
double confidence = instantiateChild(followOnConfiguration, task, result)
.checkCandidateOwner(correlationContext, candidateOwner, result);
return confidence >= correlatorContext.getOwnerThreshold();
}

/** Converts internal {@link MatchingResult} into "externalized form" of {@link CorrelationResult}. */
Expand All @@ -307,14 +309,21 @@ private double checkCandidateOwnerUsingCorrelator(
candidateOwnersMap.put(
candidate,
referenceId,
potentialMatch.getConfidenceScaledToOne());
getConfidence(potentialMatch));
} else {
LOGGER.debug("Potential match with no corresponding user: {}", potentialMatch);
}
}
}
return CorrelationResult.of(candidateOwnersMap);
}

private double getConfidence(PotentialMatch potentialMatch) {
double confidenceLimit = Objects.requireNonNullElse(
configurationBean.getCandidateConfidenceLimit(), DEFAULT_CONFIDENCE_LIMIT);
Double confidence = potentialMatch.getConfidenceScaledToOne();
return confidence != null && confidence <= confidenceLimit ? confidence : confidenceLimit;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public void test190CorrelateUsingIdMatchService() throws Exception {
CORRELATOR_ID_MATCH,
FILE_USERS_TRADITIONAL,
FILE_ACCOUNTS_ID_MATCH,
DESCRIBE_ONLY, // the confidence for potential matches is currently wrong (always 0)
FULL,
accounts -> {
// We need a specific record in our ID Match service.
ShadowType ian1 = CorrelatorTestUtil.findAccount(accounts, 1).getShadow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ uid | givenName | familyName | dateOfBirth | nationalId | expCandidates | expRes
1 | Ian | Smith | 2004-02-06 | 0402061328 | ismith:1.0 | ismith | gn:F,fn:F,dob:F,id:F | Dummy ID Match maps this account to emp #9481
2 | Jan | Smith | 2004-02-06 | 0402061328 | ismith:1.0 | ismith | gn:N,fn:F,dob:F,id:F | Dummy ID Match maps this account to emp #9481
3 | Nobody | Nobody | 0000-00-00 | 0000000000 | | _none | | There is no such person in ID Match (therefore also not in repository)
4 | Ian | Smith | 2004-02-06 | 040206---- | ismith:0.5 | _uncertain | gn:F,fn:F,dob:F,id:N | ID Match cannot decide for sure, and ismith is a candidate
4 | Ian | Smith | 2004-02-06 | 040206---- | ismith:0.4 | _uncertain | gn:F,fn:F,dob:F,id:N | ID Match cannot decide for sure, and ismith is a candidate (uncertain result confidence is limited to 0.4)
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
</description>
<!-- No url nor credentials. A dummy service is used instead. -->
<referenceIdProperty>employeeNumber</referenceIdProperty>
<candidateConfidenceLimit>0.4</candidateConfidenceLimit>
</idMatchCorrelator>

0 comments on commit 3e0af32

Please sign in to comment.