User synchronization job between Azure AD and Polarion

This Polarion job synchronizes users between Azure AD groups and Polarion: it creates Polarion accounts
for users that were added to the configured AAD groups and removes (or marks as inactive) Polarion
accounts for users that no longer belong to any of those groups.

Important

Starting from version 3.0.0 only latest version of Polarion is supported.
Right now it is Polarion 2606.

Quick start

The latest version of the extension can be downloaded from the releases page and installed to Polarion instance without necessity to be compiled from the sources.
The extension should be copied to <polarion_home>/polarion/extensions/ch.sbb.polarion.extension.aad-synchronizer/eclipse/plugins and changes will take effect after Polarion restart.

Important

Don't forget to clear <polarion_home>/data/workspace/.config folder after extension installation/update to make it work properly.

Polarion configuration

To run this job on a schedule, configure it in the global Administration / Scheduler as follows:

<job id="aad_user_synchronization.job" cronExpression="0 0 0 * * ?" name="AAD Synchronization" scope="system">
    <authenticationProviderId>oauth2</authenticationProviderId>

    <!-- Optional: override Graph property names when they differ from authentication.xml <mapping>
         (see "Overriding Graph property names per mapping field" below) -->
    <graphIdField>onPremisesSamAccountName</graphIdField>

    <!-- One literal prefix — translated to startswith(displayName, ...) on Microsoft Graph. -->
    <groupPrefixes>
        <groupPrefix>SOME_GROUP_PREFIX_</groupPrefix>
    </groupPrefixes>

    <!-- Multiple disjoint prefixes are OR-combined into one Graph $filter request:
         startswith(displayName, 'LEGACY_') or startswith(displayName, 'NEW_')
         Up to 15 prefixes are accepted; Graph rejects larger expressions with HTTP 400. -->
    <!--
    <groupPrefixes>
        <groupPrefix>LEGACY_</groupPrefix>
        <groupPrefix>NEW_</groupPrefix>
    </groupPrefixes>
    -->

    <!-- Optional: regex list applied client-side against AAD group displayName (full match).
         A group is included when ANY pattern matches. Combined with groupPrefixes the two
         selectors are unioned (independent OR-sources) — prefixes fetch a server-filtered set,
         patterns fetch the full tenant + filter client-side, results are deduped by group id. -->
    <!--
    <groupPatterns>
        <groupPattern>^SOME(_OTHER)?_GROUP_PREFIX_.*</groupPattern>
    </groupPatterns>
    -->

    <whitelist>
        <filter>^user\d{3}@example\.com$</filter>
        <accounts>
            <account>userAAA@example.com</account>
            <account>userBBB@example.com</account>
        </accounts>
    </whitelist>
    <blacklist>
        <filter>^admin|guest|service$</filter>
        <accounts>
            <account>testadmin@example.com</account>
            <account>root@example.com</account>
        </accounts>
    </blacklist>

    <dryRun>true</dryRun>
    <checkLastSynchronization>false</checkLastSynchronization>

    <!-- Optional: dumps every Graph response as full pretty-printed JSON instead of the default
         compact one-line-per-entity summary. Use only for one-off diagnostics. -->
    <!--
    <verboseGraphLog>true</verboseGraphLog>
    -->
</job>

Parameters overview

This configuration defines the parameters for the job that synchronizes users from Azure Active Directory (AAD) to Polarion.

Authentication Provider Configuration

The extension uses tokenUrl, clientId, clientSecret, scope and mapping from this provider to talk to the MS Graph API.

Example of authentication.xml:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<authentication xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns="http://polarion.com/PolarionAuthentication"
                xsi:schemaLocation="http://polarion.com/PolarionAuthentication http://localhost/polarion/authentication.xsd">

    <password default="true"/>

    <oauth2 id="oauth2">
        <nonce/>
        <view>
            <text>Single sign-on</text>
        </view>

        <authorizeUrl>https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize</authorizeUrl>

        <tokenUrl>https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token</tokenUrl>
        <clientId>client_id_goes_here</clientId>
        <clientSecret userAccountVaultKey="polarion_vault_key_for_client_secret"/>
        <scopes>
            <scope>https://graph.microsoft.com/.default</scope>
        </scopes>

        <mapping>
            <id>mailNickname</id>
            <name>displayName</name>
            <email>mail</email>
        </mapping>

        <autocreate>
            <enabled>true</enabled>
            <globalRoles>
                <role>user</role>
            </globalRoles>
        </autocreate>

        <groupsSynchronization>
            <enabled>true</enabled>
            <groupsMapping>
                <namePath>roles</namePath>
            </groupsMapping>
        </groupsSynchronization>
    </oauth2>
</authentication>

Overriding Graph property names per mapping field

The <mapping> block in authentication.xml is shared between Polarion (which uses it at login
time to read claims from the OAuth2 token) and this synchronizer (which uses it at sync time as the
Microsoft Graph property names). When the claim name on the token side differs from the property
name on the Graph side, point the synchronizer at the correct Graph property via one of these
optional job parameters:



Each override, when set to a non-blank value, replaces the corresponding <mapping> entry in the
Graph $select verbatim. Put in a standard built-in property (onPremisesSamAccountName,
userPrincipalName, …) or the fully-qualified name of an
Azure AD directory schema extension
(extension_<appIdNoDashes>_<field>).



Example — standard Graph property under a different name. The OAuth2 token exposes a custom
mycustomid claim; in Graph the same logical identifier is stored in the built-in
onPremisesSamAccountName property:

<!-- authentication.xml: claim names Polarion reads from the token -->
<mapping>
    <id>mycustomid</id>
    <name>displayName</name>
    <email>mail</email>
</mapping>
<!-- job configuration: Graph property names the synchronizer queries -->
<graphIdField>onPremisesSamAccountName</graphIdField>

The synchronizer will query Graph with $select=onPremisesSamAccountName,displayName,mail and use
the onPremisesSamAccountName value as the Polarion user identifier. Polarion keeps using the
mycustomid claim at login time because <mapping> is unchanged.

Example — directory schema extension. The custom user identifier is stored in a Graph schema
extension owned by an AAD application with id abc123de-f456-7890-abcd-ef1234567890:

<!-- job configuration: fully-qualified extension property name (app id without dashes) -->
<graphIdField>extension_abc123def4567890abcdef1234567890_mycustomid</graphIdField>

Note

Members of the AAD group are first listed via /groups/{id}/members to obtain their AAD object IDs,
and then each user is fetched individually via /users/{aadObjectId}. The per-user call is required
because the /groups/{id}/members endpoint returns directory objects that strip extension attributes
via $select.


Group Synchronization

At least one of groupPrefixes or groupPatterns must be provided. The two
selectors are independent OR-sources and produce a union of groups:

When only groupPatterns is set, all groups in the tenant are fetched and filtered
client-side — prefer to also set groupPrefixes on large tenants to keep at least one of the
two Graph responses bounded.

User Filters

Whitelist (whitelist)

If both filter and accounts are not specified, all found users will be synchronized.
If both filter and accounts are provided, the users that match the filter or are explicitly listed will be synchronized.

Blacklist (blacklist)

If both filter and accounts are not specified, no users will be excluded.
If both filter and accounts are provided, the users that match the filter or are explicitly listed will be excluded from synchronization.

Additional Settings

REST API

This extension provides REST API. OpenAPI Specification can be obtained here.