midPoint にCSVの源泉データをインポートする（組織編）

# はじめに

[midPoint by OpenStandia Advent Calendar 2024](https://qiita.com/advent-calendar/2024/midpoint-by-openstandia) の8日目は、[5日目](https://qiita.com/wadahiro/items/a239a743af5404a7325f)に続いてmidPointの設定を行い、今度は組織情報を含むCSV形式の源泉データをmidPointにインポートします。

:::note info
5日目で構築した環境が前提となります。
:::


# 源泉データの準備

サンプルの組織情報を含む源泉データを準備します。なお、このダミーの組織情報もChatGPTに生成してもらいました。`parentOrgId`は親組織IDです。これで組織ツリーを表現しています。なお、親子関係を作るために親から先に登録していく必要があるため、CSVは親から子の順番でソートしたものを用意します。


```csvs
orgId,orgName,parentOrgId
001,サンプル株式会社,
002,営業本部,001
003,総務本部,001
004,IT本部,001
005,営業第一部,002
006,営業第二部,002
007,総務部,003
008,IT開発部,004
009,ITインフラ部,004
010,営業第一課,005
011,営業第二課,006
```

上記内容のCSVファイルを作成し、`hr`ディレクトリを作成してその配下に`orgs.csv`として保存しておきます。これでmidPointからは、`/var/lib/hr/orgs.csv`でCSVファイルにアクセス可能になります。

```
.
├── .env
├── docker-compose.yml
├── hr
│   ├── orgs.csv
│   └── users.csv
└── post-initial-objects
    ├── 000-system-configuration.xml
    ├── 100-resource-hr-users.xml
    └── 900-task-hr-import-users.xml
```

# midPointの起動

`docker compose up -d`で環境を起動します。

# リソース設定の追加

midPointの画面を操作して、[5日目](https://qiita.com/wadahiro/items/a239a743af5404a7325f)と同様にCSVをインポートするためのリソース設定を追加していきます。例によってシステム管理者（`administrator`）でmidPointにログインし、設定作業を行います。

## リソースの作成

基本的な作成手順は、[5日目](https://qiita.com/wadahiro/items/a239a743af5404a7325f)で解説したユーザー用の設定と同じです。詳しい操作内容は[5日目](https://qiita.com/wadahiro/items/a239a743af5404a7325f)を参照していただき、今回は、異なる部分を中心に補足説明します。

リソースの基本情報で設定する「名前」はリソース内で一意である必要があるため、今回は`HR Orgs`としておきます。今回も、「ライフサイクル状態」は `Active (production)` とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/a3131204-511f-87b4-2bbb-2802954ad802.png)

「File path」は先程配置したダミー組織のCSVファイルパスになります。`/var/lib/hr/orgs.csv`を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/6f6faebe-4f87-8958-416c-c4ea9897723f.png)

CSVコネクターの設定は以下のようにします。

- **Field delimiter**：`,`
- **User password attribute name**：（空のまま）
- **Name attribute**：`orgId`
- **Unique attribute name**：`orgId`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/c62e7d35-0a12-b0f3-0356-de5567f13b16.png)

コネクターがサポートするオブジェクトタイプは、組織であってもデフォルトで選択済みの「AccountObjectClass」を使用します。そのまま「リソースの作成」ボタンをクリックします。

:::note info
組織なのにAccountObjectClass？と違和感があるかもしれません。CSVコネクターは1つのCSVファイルに1つのオブジェクトタイプのデータが格納されていることを想定して実装されています。この時のオブジェクトタイプは「AccountObjectClass」で固定化されています。ですが、ユーザーデータに限らないCSVファイルを利用しても問題ありません。最終的にmidPointの設定で、コネクター側のオブジェクトタイプ（今回だと「AccountObjectClass」）と、midPoint側のオブジェクトタイプ（今回だと「組織」）のマッピングをすれば問題ありません。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/c49f2c89-4fd1-fc13-b16b-3bc74717f494.png)


## オブジェクト・タイプの追加

続いてオブジェクトタイプの追加です。組織を扱うため、今回は種類には `エンタイトルメント` を選択しておきます。

:::note info
オブジェクトタイプには、「アカウント」「エンタイトルメント」「汎用」の3つから、扱うデータとして適切な種類を割り当てればよいです。
:::

- **表示名**：`Org`
- **種類**：`エンタイトルメント`
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/7d41b112-b42a-9bd8-7d01-4780f957480b.png)


リソース・データの設定は、デフォルトのままで次に進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/2dc0939d-3c75-12a2-0598-ec7f79bf3ca8.png)

MidPointデータの設定では、**組織** を選択して保存します。この設定により、CSVから取り込んだデータはmidPoint内の組織（OrgType）オブジェクトにマッピングされます。

- **タイプ**：`組織`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/4da5450e-f28f-4f69-ec5a-969d070a3baa.png)


### マッピングの設定

以下の表を参考にインバウンドマッピングを設定します。`parentOrgId` はXML編集で設定するため、「ターゲット」は今は未設定にしておきます。これで一旦保存します。

| From Resource Attribute | 式   | ターゲット        |
|:------------------------|:-----|:-----------------|
| orgId                   | 現状 | name            |
| orgName                 | 現状 | displayName     |
| parentOrgId             | 現状 | （空）          |



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/752c71b9-9297-4c16-49ab-b65efe785bd1.png)


#### `parentOrgId`より親組織をアサインするマッピングを設定

CSVでは親組織のIDを`parentOrgId`列で表現しています。midPointの組織では、親組織をアサインすることで親子関係を表現できるようになりますので、この`parentOrgId`の値を元に、親組織を探し出して自動アサインするようにマッピング設定を行います。

「指定した条件でオブジェクトを検索して、それをアサインとしてマッピング」を行う`<assignmentTargetSearch>`という特別なタグがあるので、これを利用します。

作成したリソース設定の「RAWデータの編集」ボタンをクリックして、Raw編集画面を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/e3d5cb3b-e3bc-0d5a-1a18-11500ccf858e.png)

`parentOrgId`の`<inboud>〜</inbound>`に、以下のようにXML設定を追加します。

```diff_xml: HR OrgsリソースのRaw編集
            <attribute id="11">
                <ref>ri:parentOrgId</ref>
                <inbound id="12">
                    <strength>strong</strength>
+                   <expression>
+                       <assignmentTargetSearch>
+                           <targetType>c:OrgType</targetType>
+                           <filter>
+                               <q:equal>
+                                   <q:path>name</q:path>
+                                   <expression>
+                                       <queryInterpretationOfNoValue>filterNone</queryInterpretationOfNoValue>
+                                       <script>
+                                           <code>
+                                               input
+                                           </code>
+                                       </script>
+                                   </expression>
+                               </q:equal>
+                           </filter>
+                           <assignmentProperties>
+                               <subtype>HR</subtype>
+                           </assignmentProperties>
+                       </assignmentTargetSearch>
+                   </expression>
+                   <target>
+                       <c:path>assignment</c:path>
+                       <set>
+                           <condition>
+                               <script>
+                                   <code>
+                                       assignment?.subtype.contains("HR")
+                                   </code>
+                               </script>
+                           </condition>
+                       </set>
+                   </target>
                </inbound>
            </attribute>
```

`<filter>`では、`name`属性に対して`input`変数（`<ref>`で設定した`parentOrgId`の値が格納される）を使って一致検索を行うように設定しています。また、`<queryInterpretationOfNoValue>filterNone</queryInterpretationOfNoValue>`の設定により、`input`が値なしの場合（CSVの`parentOrgId`が未設定の場合）に、`filterNone`と評価されるようにしています。これにより、最上位のルート組織では、親組織の検索結果が0件、つまり何もアサインしないようにしています。

`<assignmentProperties>`は、アサインに含めることができるメタデータを設定するためのタグです。今回、`subtype`というメタデータ属性に`HR`を設定しています。このアサインが源泉データ起因で行われたことが後から分かるように印をつけています。

`<target>`では、アサインデータのマッピング先を設定しています。midPointの組織オブジェクトが持つ`assignment`属性に格納するように設定します。その中で重要なのが、`<set>`の設定です。これは、[7日目の記事で解説した高度なRange Mapping設定](https://qiita.com/wadahiro/items/e37b406385da722f54b5#%E9%AB%98%E5%BA%A6%E3%81%AArange-mapping%E8%A8%AD%E5%AE%9A)です。

`assignment`属性は、組織が複数のアサインを持てるように、マルチバリュー項目になっています。CSVの組織データをインポートする際に、既存の親子関係を洗い替えるようにしないと、旧組織への親参照をもったままになってしまいます。よってここでは、Range Mappingを設定して洗い替えを行います。

しかしながら、7日目の記事で解説した`<predefined>all</predefined>`を使うと、すべてのアサイン情報を洗い替えてしまいます。この組織にはロールなど別のオブジェクトをアサインする可能性もあるため、ここでは**源泉データよりアサインされた親組織に絞って洗い替え**ができるとよさそうです。

ということで、Groovyスクリプトを使った高度なRange Mappingを行います。では、どのようにして「源泉データよりアサインされた親組織」を判断するとよいでしょうか？ ここで役立つのが先程設定したメタデータです。アサインの`subtype`属性に、`HR`という値を設定しているため、これを確認して判断すればよいです。`subtype`はマルチバリュー項目のため、`contains`メソッドを使っていずれかの値が`HR`かどうかをチェックできます。この設定により、`HR`を`subtype`に持つアサインは削除対象となり、洗い替えが期待通りに行われます。



### 同期の設定

ユーザーの時と同じ同期のルール設定をしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/8295c990-10c2-206c-7bc1-3f44435b159d.png)


### Correlationの設定

ユーザーの時と同じCorrelationのルール設定をしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/ac32fb36-bebe-9d66-b4ee-8c78ab36dc46.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/d2b66103-4db5-a5d0-aa7a-62180ef3e332.png)


# 組織のインポート

リソースの作成が完了しましたので、いよいよ組織をインポートします。[5日目の記事](https://qiita.com/wadahiro/items/a239a743af5404a7325f)で解説したとおり、インポートタスクでは源泉データの削除を検知して同期することができません。つまり、組織改正が行われて新しい組織データを取り込んでも、廃止した組織が消えずにそのまま残ってしまいます。そこで、[6日目の記事「midPoint における源泉削除の同期処理」](https://qiita.com/wadahiro/items/5bcf320b752e696b1abb)で解説したように、削除も同期できるように今回は「リコンシリエーションタスク」を作成して一括取り込みを行います。

## リコンシリエーションタスクの作成

`Reconciliation Task`を選択して、リコンシリエーションタスクを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/b3cb6457-6c99-9a4b-8f9b-0f8c528d3a47.png)

区別が付くように名前を設定します。

- **名前**：`HR recon orgs`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/780d2a1b-e0ce-f2ad-8461-2990e2ab4d29.png)

「種類」は今回、リソースのオブジェクト・タイプの設定で**エンタイトルメント**としているため、このタスクで利用する種類も`エンタイトルメント`にする必要があります。

- **種類**：`エンタイトルメント`
- **用途**：`default`
- **オブジェクトクラス**：`AccountObjectClass`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/f4f59135-ee0c-2440-1089-17f282a0da34.png)

スケジュールは、今回も空のままとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/a93041bd-fe04-024a-401f-ddb54eee5e26.png)

ワーカースレッドは`1`を設定します。親から子の順番で処理しないと親子関係の設定に失敗するため、マルチスレッドでは処理しないようにしておきます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/cbd1bbec-17fa-c27c-f4e7-407de83269fd.png)

## リコンシリエーションタスクの実行

作成したリコンシリエーションタスクを実行すると、CSVより組織データがインポートされ、親子関係もできていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/0934ff03-6b4a-66ac-2709-0848b13e7f61.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/b035db52-599f-57cc-caba-d6b34ce88ad9.png)


# 組織改正

では、最後に組織改正のシナリオも試してみましょう。ここでもChatGPTを使って組織改正後のダミーデータを作ってもらいました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/5a48c2ae-308f-48d6-d052-f207b29028a3.png)

というわけで、`hr/orgs.csv`を以下の内容に置き換えます。


```csvs
orgId,orgName,parentOrgId
001,サンプル株式会社,
002,営業統括本部,001
003,管理本部,001
004,デジタル戦略本部,001
005,法人営業部,002
006,個人営業部,002
007,総務・人事部,003
008,ITソリューション部,004
009,インフラ運用部,004
012,営業推進課,005
013,DX推進課,004
014,新規事業部,001
```


これで再度リコンシリエーションタスクを実行します。実行完了後、組織ツリーを確認すると組織改正後の状態になっていることを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/9a877352-9c7c-2356-4141-db0b16bfc892.png)

監査ログも確認してみましょう。リソース設定にて、同期の設定で「状況（Situation）が Deleted」の場合にフォーカス削除としているため、リコンシリエーションタスクを使用することで、廃止組織（`orgId`が`010`と`011`の組織）がmidPointから削除されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35233/1ed3843e-bc82-be7b-6b89-25bf89ab7548.png)


# まとめ

8日目では、組織情報を含むCSV形式の源泉データを、midPointの組織オブジェクトとしてインポートするための設定を紹介しました。また、[6日目の記事](https://qiita.com/wadahiro/items/5bcf320b752e696b1abb)と[7日目の記事](https://qiita.com/wadahiro/items/e37b406385da722f54b5)で補足説明した設定を利用して、組織改正にも対応させました。

これでユーザーと組織を源泉データよりmidPointに取り込むことができるようになりました！しかし、お気付きかもしれませんが、midPoint上でユーザーと組織の関連付け（アサイン）がまだ行われていません。次回はこのあたりの設定追加を行います。お楽しみに！