ImportTechnicalAccountsFromCSVExecutor

Long Running Task (LRT) that bulk-creates or updates Technical Accounts in CzechIdM by reading a semicolon-separated CSV file. Each CSV row represents one technical account. The task maps configurable column names to entity fields, validates the whole row before it writes anything, supports dry-run mode, and never re-points an existing account to a different provisioning system.

Version Compatible with product Notes
15.3.0 15.15.1 First implementation of the task.
  • The task reads the attached CSV file line by line (first line = header).
  • For each data row it looks up an existing Technical Account by the value in the identifier column (matched against code) scoped to the system named in systemcolumn. The same code may legally exist on different systems - the lookup returns only the account linked to the row's system, if any.
  • Depending on whether the account exists and which behaviour flags are set, the account is created, updated or skipped.
  • Before anything is written, the whole row is validated in one gate. Every problem found in the row is reported together and the row is aborted without a partial write.
  • Optional columns assign identity and role guarantors, link the account to a provisioning system and associate it with a Technical Asset.
  • All item-level results are written to the LRT task log.
Property Value
Encoding UTF-8
Column separator ; (configurable)
First row Header with column names
Multi-values | by default (configurable)
Date format yyyy-MM-dd (ISO 8601)
Only the identifier column is mandatory. All other columns are optional - the task reads a column only when the corresponding parameter is configured and the column exists in the CSV header. Configuring a column name that is absent from the header aborts the whole task with an error listing the missing columns.

The header check runs on the first data row, so a header-only file is accepted with any column configuration, and it validates column-name parameters only - multivalueseparator and the behaviour flags are not column names.
Parameter Type Required Default Description
importFile Attachment Yes - CSV file to import
separator String No ; Column separator character
encoding String No utf-8 File encoding

Each of the column parameters below is the exact name of a column in the CSV header (case-sensitive). multivalueseparator is the exception - it is a value, not a column name.

Parameter Description
identifiercolumn Required. Unique identifier of the account; stored as code and used as the uid of the AccAccount created for a new account. Used to look up existing accounts.
namecolumn Technical account name (maps to externalCode - the Name field in the UI). Written unconditionally when the parameter is configured, so a blank cell clears the existing name on update. Leaving the parameter unconfigured keeps the current value.
descriptioncolumn Free-text description. Same blank-cell rule as namecolumn - a blank cell clears the value.
validfromcolumn Validity start date (yyyy-MM-dd). A blank cell leaves the field unchanged.
validtillcolumn Validity end date (yyyy-MM-dd). A blank cell leaves the field unchanged.
systemcolumn Name (codeable identifier) of the provisioning system. Required when creating new accounts; for existing accounts it is a consistency check, see below.
mappingcolumn Provisioning mapping name. Must be a TECHNICAL_ACCOUNT PROVISIONING mapping on the system.
directidentitycolumn Identity guarantors - list of usernames separated by the multi-value separator.
guaranteerolecolumn Role guarantors - list of role codes separated by the multi-value separator.
technicalassetcolumn Code of the Technical Asset to associate with the account.
tiercolumn Tier written to the AccAccount linked to the account, for both new and existing accounts. A blank cell or an unconfigured column keeps the current value; it never clears it.
zonecolumn Zone written to the AccAccount linked to the account, for both new and existing accounts. Same blank-cell rule as tiercolumn.
disabledcolumn Disabled status of the account. `1`, `true` and `yes` mean disabled, `0`, `false` and `no` mean enabled; matching is case-insensitive and the cell is trimmed. A blank cell leaves the value unchanged. Any other value fails the whole row.
multivalueseparator Character separating values in multi-value columns. Default |.
Parameter Type Default Description
cancreate Boolean false Allow creating new Technical Accounts. When false, rows whose identifier is not found are logged as skipped.
canoverwrite Boolean false Allow updating existing accounts. When false, an existing account is skipped entirely - no field, no guarantor and no AccAccount change. When true, every configured column of the row is applied: externalCode, description, validFrom, validTill, disabled, the technical asset (still subject to canchangeta) and the tier and zone of the linked AccAccount; guarantors from the row are added as well.
canchangeta Boolean false Gates a change of the technical asset on an already existing account. It does not switch the column off - see Technical Asset Assignment.
read identifier from identifiercolumn
read system name from systemcolumn (if configured)
  |- find existing TechnicalAccount by code = identifier
  |    (scoped to the system from systemcolumn - see Code and Name Uniqueness Rules)
  |- NOT FOUND + cancreate = false   ->  log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop
  |- FOUND + canoverwrite = false    ->  log CSV_IMPORT_ACCOUNT_SKIPPED (EXCEPTION), stop

validatePreSave - one gate, nothing is written yet
  |- new account: resolve system and mapping (mandatory), check the code is free on that system
  |- existing account: check the linked AccAccount against systemcolumn / mappingcolumn
  |- resolve every identity guarantor and every role guarantor
  |- resolve the technical asset by code, apply the canchangeta rule
  |- parse the disabled cell
  |- run the asset assignment check script for the resulting (asset, account) pair
  |- any problem  ->  all problems joined into one CSV_IMPORT_ACCOUNT_ERROR, row aborted

write
  |- existing account: tier / zone written to the linked AccAccount and saved FIRST
  |- save the technical account (the technical asset is already set on it)
  |- new account: create the AccAccount (uid = identifier) and the TechnicalAccountAccount link
  |- add identity and role guarantors
Every reference in the row is resolved before the first write. An identity username, a role code or a technical asset code that cannot be resolved aborts the row with CSV_IMPORT_ACCOUNT_ERROR - the account is neither created nor updated and no guarantor is assigned. All problems found in the row are reported in a single message. The import continues with the following rows.

directidentitycolumn and guaranteerolecolumn can contain multiple values separated by the multi-value separator (default `|`). Whitespace around each value is trimmed.

Example cell value:

jnovak|pdvorak|mmaly

Each value is resolved independently, but a value that cannot be resolved does not get skipped - it fails the whole row together with every other problem found in it.

Enter the separator as a plain character (`|`, `,`, `;`, `#`). The task escapes it for its internal regular expression itself, so a regex metacharacter is matched literally and must not be pre-escaped by the operator. An already escaped value would be escaped again on the next run and splitting would break.
The task only adds guarantors - it never removes them. Guarantors assigned previously, manually or by an earlier import, are left untouched regardless of what the current CSV contains.

Every Technical Account is expected to have exactly one AccAccount. The link is maintained via the TechnicalAccountAccount join entity.

When cancreate = true and the identifier is not found:

  • systemcolumn is mandatory. If it is not configured or the system is not found in CzechIdM, the row fails with CSV_IMPORT_ACCOUNT_ERROR.
  • If mappingcolumn is configured, the named mapping must exist on the system and must be of type TECHNICAL_ACCOUNT PROVISIONING. Otherwise the row fails.
  • If mappingcolumn is not configured, the system must have exactly one TECHNICAL_ACCOUNT PROVISIONING mapping. Zero or more than one also fails the row.
  • Once system and mapping are validated, a new AccAccount is created (uid = identifier) and linked via TechnicalAccountAccount.
Scenario Behaviour
systemcolumn not configured Error - row fails with CSV_IMPORT_ACCOUNT_ERROR.
System name not found in CzechIdM Error - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping specified, not found on system Error - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping specified, found but not TECHNICAL_ACCOUNT type Error - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping not specified, no TECHNICAL_ACCOUNT mapping on system Error - row fails with CSV_IMPORT_ACCOUNT_ERROR.
Mapping not specified, multiple TECHNICAL_ACCOUNT mappings on system Error - specify the mapping explicitly; row fails with CSV_IMPORT_ACCOUNT_ERROR.
System and mapping resolved successfully New AccAccount (uid = identifier) created; TechnicalAccountAccount link saved.

The system, mapping and uid of the linked AccAccount are never changed and no new AccAccount or link is created. The tier and zone of the linked AccAccount are written when the corresponding columns are configured.

The systemcolumn and mappingcolumn values are a hard consistency check: a mismatch fails the row.

Scenario Behaviour
systemcolumn not configured or the cell is blank System check skipped.
System name not found in CzechIdM The lookup degrades to code-only, so the existing account is still found, and the row then fails on the system mismatch with CSV_IMPORT_ACCOUNT_ERROR.
AccAccount is on a different system than specified Row fails with CSV_IMPORT_ACCOUNT_ERROR; nothing is written.
mappingcolumn not configured Mapping check skipped.
AccAccount uses a different mapping than specified Row fails with CSV_IMPORT_ACCOUNT_ERROR; nothing is written.
The account has no linked AccAccount (orphan) or the link is dangling The whole consistency check is skipped, tier and zone are not written and the row succeeds.
Why is the AccAccount system and mapping never changed?
If a Technical Account already exists in CzechIdM, its provisioning links were set up previously, manually or by an earlier import run. Re-pointing the AccAccount to another system or mapping could make CzechIdM's provisioning engine act on a live system. Tier and zone are the deliberate exception - they are attributes of the account, not of its link to a system.
A row that updates an existing account is written in two steps and they are not covered by one transaction.
The linked AccAccount (tier, zone) is saved first, the technical account second. The order is deliberate: saving the technical account triggers provisioning, which re-saves the AccAccount in its own event where the skip flag no longer applies, so the account has to carry the target state by then. A failure that the pre-save gate cannot predict - a duplicate externalCode is the realistic example, because it is validated by the framework at save time - therefore leaves the AccAccount already updated while the technical account is not.

Technical assets are looked up by their code (TechnicalAsset.code), not by the Name field. An asset code that cannot be resolved fails the row.

canchangeta does not switch the column off. The column is always read and the asset always resolved; the flag only gates a real change on an already existing account.

Scenario Behaviour
New account, any value of canchangeta The asset from the CSV is assigned. The flag does not apply - there is no current asset to change.
Existing account, cell empty No-op, the row succeeds and the current asset is kept.
Existing account, asset equals the currently assigned one No-op, the row succeeds.
Existing account, asset differs, canchangeta = false Row fails with CSV_IMPORT_ACCOUNT_ERROR; nothing from the row is written.
Existing account, asset differs, canchangeta = true The asset is changed.
Asset code not found Row fails with CSV_IMPORT_ACCOUNT_ERROR.

Whenever a row ends up with a technical asset, the pair (asset, target AccAccount) is validated by a configurable Groovy script through TechnicalAssetService.isAssetAssignable. The script is named by the private configuration property idm.sec.tech.accountAssetAssignment.checkScript.

# Script deciding whether a technical asset may be assigned to an account.
# Default: nothing is enforced out of the box.
idm.sec.tech.accountAssetAssignment.checkScript=techAccountAssetAssignmentAllowAll
  • techAccountAssetAssignmentAllowAll - the shipped default, always returns true.
  • techAccountAssetAssignmentZoneMatch - requires the asset zone and the AccAccount zone to match; an asset without a zone is always accepted.

The check is evaluated against the effective asset (the one from the CSV, or the one already assigned when the CSV does not change it, so it also runs on rows that do not mention an asset) and the target AccAccount, that is the account as it will look once the row's tier and zone are applied. When the script rejects the pair, the row fails with CSV_IMPORT_ACCOUNT_ERROR before anything is written.

The task suppresses the runtime assignment-check processors on its own saves (event property SKIP_ACCOUNT_ASSET_ASSIGNMENT_CHECK). That is why it has to evaluate the script itself up front: the row is written in two steps and the intermediate state between them would otherwise be rejected. The verdict is the same one the processors would give, only it is known before the first write and it holds in dry-run as well.
  • code is unique per system. The same code may exist on different systems - each combination (code, system) yields its own Technical Account.
  • The lookup combines code with the system named in systemcolumn. An account linked to a different system is invisible to the lookup, so a new account is created for the row's system.
  • The lookup degrades to code-only in three cases: systemcolumn is not configured, the cell is blank, or the system named in the cell does not exist in CzechIdM. Uniqueness is then not enforced either - there is no system context to validate against, so "orphan" accounts (those without an AccAccount link) are not constrained on code at all and several orphans with the same code may coexist.
  • For a new account the task checks up front that the code is free on the target system and fails the row otherwise. The product-side processors (TechnicalAccountCodeCheckProcessor, TechnicalAccountAccountCodeCheckProcessor) are the second line of defence; on the import's create path only the latter can still raise TECHNICAL_ACCOUNT_ALREADY_EXISTS_ON_SYSTEM, when the link is saved.
  • externalCode (FE label "Name") must be unique across all Technical Accounts in CzechIdM, regardless of which system the account is linked to.
  • A blank namecolumn cell (resulting in an empty externalCode) skips this validation.
  • Enforced by the framework in AbstractReadWriteDtoService.validateEntity. The conflict surfaces as DuplicateExternalCodeException at save time, which the task records on the item as CSV_IMPORT_ACCOUNT_ERROR.
Scenario Outcome
Same code on two different systems in the CSV OK - two separate Technical Accounts are created, one per system.
Same code on the same system in the CSV, canoverwrite = false Second row is skipped - the first row's account is found and not overwritten.
Same code on the same system in the CSV, canoverwrite = true Second row updates the first row's account.
Same name on two rows (any systems) Second row fails - externalCode is globally unique.
systemcolumn blank, identifier matches an orphan account The lookup finds the orphan and proceeds with the update.
Practical impact for CSV imports: the same code may appear on several rows targeting different systems and each row creates its own account.
Each row must use a globally unique name, or leave namecolumn blank. Re-running the import with names already present in the database fails those rows as well.

The task supports CzechIdM's built-in dry-run flag. When dry-run is enabled:

  • No Technical Accounts are created or updated.
  • No guarantors are assigned.
  • No AccAccounts or TechnicalAccountAccount links are created and no tier or zone is written.
  • Rows that would be created or updated are logged with CSV_IMPORT_ACCOUNT_WOULD_BE_CREATED or CSV_IMPORT_ACCOUNT_WOULD_BE_UPDATED and state NOT_EXECUTED.
  • Skipped and failing rows are logged exactly as in a real run, with state EXCEPTION - a dry run therefore reports the same failures a real run would hit, including a rejection by the assignment check script.
  • Would-be operations are written to the application log at INFO level (prefix [DRY-RUN]).
  • The task counter still increments, so the row count can be verified.
To execute a dry run, enable the Dry run toggle when scheduling the task in the UI, or set dryRun = true on the IdmLongRunningTaskDto before calling LongRunningTaskManager.executeSync() in code.
Code State Counted as Description
CSV_IMPORT_ACCOUNT_CREATED EXECUTED successItemCount Technical account was created.
CSV_IMPORT_ACCOUNT_UPDATED EXECUTED successItemCount Technical account was updated.
CSV_IMPORT_ACCOUNT_WOULD_BE_CREATED NOT_EXECUTED warningItemCount Dry-run: the account would be created.
CSV_IMPORT_ACCOUNT_WOULD_BE_UPDATED NOT_EXECUTED warningItemCount Dry-run: the account would be updated.
CSV_IMPORT_ACCOUNT_SKIPPED EXCEPTION failedItemCount Row skipped (cancreate = false or canoverwrite = false).
CSV_IMPORT_ACCOUNT_ERROR EXCEPTION failedItemCount Row failed. This is the single code for every row-level failure - unresolved guarantors or asset, an unknown or mismatched system or mapping, a non-boolean disabled value, a would-be asset change with canchangeta = false, a duplicate code, a rejection by the assignment check script, and any exception raised while the row was being written. The original message is carried as the reason.
The counters follow the operation state, not the HTTP status: EXECUTED and CREATED count as success, EXCEPTION as failed and everything else, including NOT_EXECUTED, as a warning.

Only identifier and name columns - no system, guarantors or asset.

identifiercolumn;namecolumn
svc-api-gateway;API Gateway
svc-db-reader;DB Reader
svc-batch-runner;Batch Runner

Task configuration:

Parameter Value
identifiercolumn identifiercolumn
namecolumn namecolumn
cancreate true
canoverwrite false
identifiercolumn;namecolumn;descriptioncolumn;validfromcolumn;validtillcolumn;systemcolumn;mappingcolumn;directidentitycolumn;guaranteerolecolumn;technicalassetcolumn
svc-ldap-reader;LDAP Reader;LDAP read-only service account;2024-01-01;;CMS AD - Users;AD users provisioning mapping tech.;jnovak|pdvorak;role-ldap-admin|role-infra-team;CORE-INFRA
svc-db-backup;DB Backup;Database backup service account;2024-01-01;2026-12-31;CMS AD - Users;AD users provisioning mapping tech.;mpolak;role-dba;DATABASES

Task configuration:

Parameter Value
identifiercolumn identifiercolumn
namecolumn namecolumn
descriptioncolumn descriptioncolumn
validfromcolumn validfromcolumn
validtillcolumn validtillcolumn
systemcolumn systemcolumn
mappingcolumn mappingcolumn
directidentitycolumn directidentitycolumn
guaranteerolecolumn guaranteerolecolumn
technicalassetcolumn technicalassetcolumn
multivalueseparator |
cancreate true
canoverwrite true
canchangeta true
The technicalassetcolumn cell holds the asset code, not its name. A blank validtillcolumn cell (like the first row above) leaves the field unchanged - it does not clear an existing validTill date.
  • by koulaj