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.
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.
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>This configuration defines the parameters for the job that synchronizes users from Azure Active Directory (AAD) to Polarion.
authenticationProviderId): the OAuth2 provider from authentication.xml used to obtain authentication tokens for the Microsoft Graph API and to determine the field mappings.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>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:
graphIdField: Graph user property to use as the Polarion identifier.graphNameField: Graph user property to use as the display name.graphEmailField: Graph user property to use as the email.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.
groupPrefixes): A list of literal prefixes against AAD groupdisplayName. The job translates the list into a single Microsoft Graph request whose$filter OR-combines startswith(displayName, ...) clauses for every entry. Up to 15LEGACY_ and NEW_).groupPatterns): Optional list of regular expressionsjava.util.regex syntax,String.matches) applied client-side against displayName after fetching^(LEGACY|NEW)_TEAM_.* matchesAt least one of groupPrefixes or groupPatterns must be provided. The two
selectors are independent OR-sources and produce a union of groups:
groupPrefixes only — one Graph request with a server-side startswith($filter).groupPatterns only — one unfiltered Graph request, regex applied client-side.TEAM_ plus pattern^ADMIN_SPECIAL$).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.
whitelist)filter): A regex pattern to include matching users.accounts): A predefined list of users to include in synchronization.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)filter): A regex pattern to exclude matching users.accounts): A predefined list of users to always exclude from synchronization.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.
dryRun): Enables simulation mode, where no actual changes are made.checkLastSynchronization): Determines whether to verify the timestamp of the last synchronization before execution.verboseGraphLog): Optional. Default false. When false, every Microsoft+N more suffix). Set to true to fallfalseThis extension provides REST API. OpenAPI Specification can be obtained here.