> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/lifecycle/synchronize-and-import-data/import-from-two-authoritative-sources.md).

# Import from two authoritative sources without overwriting

Run an AD import and an Entra import against the same people without either one overwriting what the other owns.

## What this is, and when you need it

Two feeds describing the same person do not automatically cooperate. Each one asserts whatever its transformation sets, on every run, so two feeds that both claim the same OpenIAM field will take turns overwriting it.

The fix is not a product setting. It is a field-authority decision, written down before either import runs, and then enforced by removing the fields a source does not own from that source's scripts.

The worked case here is AD and Microsoft Entra, because that is the common one. The method is the same for any two sources — an HR system and a directory, two directories, a directory and a cloud tenant.

## At a glance

|                          |                                                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | One synchronization whose scripts assert only the fields its source owns, proven by two consecutive runs that leave the other source's fields untouched |
| **Where**                | **Provisioning → Synchronization**, `/webconsole/synchronizations`; the scripts are in **Administration → Groovy Manager**                              |
| **You need first**       | An approved field-authority matrix, the immutable correlation key both sources carry, and Groovy Manager access to the narrowed feed's scripts          |
| **Takes**                | An hour or two for the matrix and the script edits, then two full runs, which scale with the source                                                     |
| **Reversible**           | Partly — selecting the previous script versions restores the behaviour, but values a run already overwrote are not restored by reverting the script     |

## Before you begin

* An approved field-authority matrix — which source owns which OpenIAM field, and where the answer is "no assertion".
* The immutable correlation key both sources carry. Not `sAMAccountName`: it is mutable, and cloud-only users do not have one.
* Groovy Manager access to the attribute-lookup, validation and transformation scripts of the feed you are narrowing, and an inactive copy of that synchronization to test on.

## Decide authority, then narrow the feed

{% stepper %}
{% step %}

### Approve the field authority matrix

Approve a matrix like this before touching either import. "No assertion" and "clear the value" are different: the transformation must **omit** the field on update unless that source is authorised to set or remove it.

| OpenIAM data           | AD import                                                  | Entra import                               | Conflict behaviour                                                            |
| ---------------------- | ---------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| Person correlation key | Read the approved immutable worker key                     | Read the same key, or a governed crosswalk | Reject zero and multiple matches. Never fall back to name.                    |
| AD account / login     | Owns `sAMAccountName`, the AD object identifier, AD status | No assertion                               | Entra cannot rename or remove it                                              |
| Entra account / login  | No assertion                                               | Owns the Entra object ID and the UPN       | AD cannot create or clear it                                                  |
| Primary email          | No assertion when AD `mail` is incomplete                  | Owns the verified cloud address per policy | A missing Entra value means no change, unless clearing is explicitly approved |
| Name, manager, status  | Name the actual HR or directory authority                  | No assertion, or explicitly lower priority | A lower-priority source cannot overwrite authoritative data                   |
| {% endstep %}          |                                                            |                                            |                                                                               |

{% step %}

### Copy the three shipped scripts to a customer path

Exclude UPN and email from the AD import when AD is not authoritative for those OpenIAM values. Three layers have to agree, or the one you miss reasserts the value — that is what steps 2 to 4 are.

Copy the shipped attribute-lookup, validation and transformation scripts into a customer-owned, versioned path. Do not edit a shipped script in place; the next upgrade replaces it.
{% endstep %}

{% step %}

### Stop asking the connector for the attributes

Remove `mail` / `EmailAddress` and `userPrincipalName` / `UserPrincipalName` from your copy of the **attribute-names lookup** script, so the connector is not even asked for them. Anything not in `getAttributes()` is not in `columnMap`.
{% endstep %}

{% step %}

### Remove the email write from the transformation

Remove the transformation logic that constructs `PRIMARY_EMAIL`, including any fallback from a missing `mail` to the UPN or a generated address.
{% endstep %}

{% step %}

### Remove email and UPN from the validation rule

Remove email and UPN from the validation rule's required set. Keep validating the immutable key, the AD account identifier and the fields AD does own.
{% endstep %}

{% step %}

### Test on an inactive copy

Select the new script versions on an **inactive copy** of the synchronization and run a bounded test with one user that has `mail` and one that does not.

{% hint style="warning" %}
Mistakes here show up as oscillating values or duplicate identities, not as a failed run — so a clean run report is not evidence. Compare both test users after two consecutive runs.
{% endhint %}
{% endstep %}
{% endstepper %}

The email material itself is not repeated here. The three shipped scripts that contain an email/UPN block, the two independent writes inside it, and the five checks to make before commenting it out are in [Editing a shipped transformation script](/administration-guide/automation/synchronization-scripts.md#editing-a-shipped-transformation-script-removing-the-email-and-upn-block); what a user without an address then loses is in [What a user with no email address misses](/administration-guide/platform/mail-and-notifications.md#what-a-user-with-no-email-address-misses). Two points from there are worth carrying into the plan for this run: existing addresses are **not** removed by a feed that stops asserting them, and on an existing user the shipped `addUserEmailAddress` call **replaces** rather than seeds.

## Verification

Test, for every pattern: a hybrid user, a cloud-only user, an AD-only user, a renamed UPN, a missing email, a duplicate proposed email, an unknown object, and a **second no-change run**. After each, confirm the person count, both managed-system identities, the primary email and the audit outcome.

The second no-change run is the one that proves the design: if a value moves between two runs of two feeds that changed nothing, the two feeds are still both asserting it.

## When it goes wrong

| Symptom                                                                   | Cause                                                                                | Fix                                                                                                                                                                        |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An attribute changes back and forth between runs of two feeds             | Both transformations assert the same OpenIAM field                                   | Remove the field from the non-authoritative feed's transformation, its validation rule and its attribute-names lookup script. All three, or the one you miss reasserts it. |
| The Entra feed creates a second identity for a person AD already imported | The two feeds correlate on different values, or on a mutable one                     | Correlate both feeds to the same immutable worker key. `sAMAccountName` is mutable and cloud-only users do not have one.                                                   |
| A user loses an address the feed stopped asserting                        | It does not — existing addresses are not removed by a feed that stops asserting them | Look for a transformation that still writes the field, on either feed.                                                                                                     |

## Rollback

Select the previous script versions on the config — which is why the scripts must live in a customer-owned versioned path rather than being edited in place — and untick **Active** until the previous versions are back in force. Values already overwritten are not restored by reverting the script; repair those from the run report of the run before the one that overwrote them.

## How the two feeds actually meet

AD and Microsoft Entra often describe the same person and do not own the same fields. A common design has AD own the on-premises account and `sAMAccountName` while Entra owns the cloud account, the UPN and the cloud mailbox. Treating both imports as full authorities produces oscillating values, false validation failures and duplicate identities.

Adding an Entra connector does **not** link an Entra record to an AD/OpenIAM user. Correlation remains the explicit `MATCH_FIELD_NAME` / `MATCH_SRC_FIELD_NAME` pair or a custom match rule. The shipped Azure sample (`AZURE_USER_SYNC`, marked *"Azure AD User Sync (Deprecated)"*) uses `MATCH_FIELD_NAME = 'EMAIL'` and `MATCH_SRC_FIELD_NAME = 'UserPrincipalName'` with scripts `sync/user/azure/AzureUserSampleValidation.groovy` and `sync/user/azure/AzureUserSampleTransformation.groovy` — matching on email against the UPN. That is a starting point for a greenfield tenant and a hazard on an existing AD population, because email is mutable and reusable.

## Correlating cloud-only users

Choose a key that exists and is unique in both the existing identity and the source. Prefer an immutable workforce ID. Do not use `sAMAccountName` as the universal person key: it is mutable, and cloud-only users do not have one.

| Population                          | Correlation                                                                                                                                                                                                                                    |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Hybrid workforce                    | Both imports correlate to the same immutable worker ID; each creates and updates only its own managed-system login and its own owned attributes                                                                                                |
| Cloud-only workforce                | Correlate on an immutable identifier from the workforce authority, or a governed crosswalk from Entra object ID to the OpenIAM person. The Entra transform may create the person only when it is the approved authority for cloud-only people. |
| Unmatched external or cloud account | Quarantine or report for correlation — this is what `IS_ORPHAN_DETECTION` is for. Never guess from display name or email alias.                                                                                                                |

Test, for every pattern: a hybrid user, a cloud-only user, an AD-only user, a renamed UPN, a missing email, a duplicate proposed email, an unknown object, and a **second no-change run**. After each, confirm the person count, both managed-system identities, the primary email and the audit outcome.

## Preserve immutable external identifiers

Directory identifiers such as AD `objectGUID` are correlation data, not display fields. Store the raw value and its source format before deriving a second representation.

For Microsoft Entra, `onPremisesImmutableId` / sourceAnchor is conventionally the Base64 encoding of the selected **binary** AD source-anchor value. Base64-encoding the printable GUID *text* is not equivalent and will not match. Use one reviewed transformation to convert the connector's representation into the exact byte representation the downstream authority requires, prove it against a known test user, rerun without change, and reject any attempt to overwrite the anchor after correlation. If users can move between forests, decide before production whether `objectGUID` remains suitable or a portable anchor such as `mS-DS-ConsistencyGuid` is required.

## Related

* [Set up a synchronization](/administration-guide/lifecycle/synchronize-and-import-data.md) — the configuration procedure for each of the two feeds.
* [Editing a shipped transformation script](/administration-guide/automation/synchronization-scripts.md#editing-a-shipped-transformation-script-removing-the-email-and-upn-block) — the exact email and UPN block, and the five checks before you remove it.
* [How a synchronization matches and processes a record](/administration-guide/lifecycle/synchronize-and-import-data/synchronization-matching-and-processing.md) — what an ambiguous match value does, and why the correlation key has to be unique.
* [Connectors and managed systems](/administration-guide/lifecycle/connectors-and-managed-systems.md) — the two connectors themselves.

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/lifecycle/synchronize-and-import-data/import-from-two-authoritative-sources.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
