unicornidm.template(5)
========================

概要
-----

::

    /opt/ossstech/unicornidm/etc/templates/<name>.py

説明
------

バックエンドセクションを定義する際に、必ずテンプレートファイルを定義する必要があります。
テンプレートファイルは unicornidm.conf と同じディレクトリにある、
templates ディレクトリ内に、 <name>.py のファイル名で登録します。 <name> はバックエンド
の名前です。

テンプレートファイルのルール
-----------------------------

テンプレートファイルは Python の構文に基づいて以下のルールで記載します。

* *User* 及び *Group* という変数に辞書オブジェクトを割り当てること

* *User* には必ず *userName* という属性 (フロントエンド属性) を含めること

* *Group* には必ず *groupName* という属性 (フロントエンド属性) を含めること

* 辞書オブジェクトは key-value の形式になっていること

* 辞書オブジェクトの key は必ず str 型であること

* 辞書オブジェクトの value は以下のいずれかであること

    - str 型

    - str 型 (printf(3) の文字列書式を含む)

    - list 型

    - dict 型

        + dict の各 value は str か任意の変数であること

    - 任意の変数

    - default 関数の返り値

    - その他、 unicornidm.template_func パッケージに定義した関数の返り値

例
-----

以下は LDAP バックエンドのテンプレートファイルです。

::

    User = {
        "objectClass": [
            "top",
            "person",
            "organizationalPerson",
            "inetOrgPerson",
            "posixAccount",
        ],
        "uid": userName,
        "cn": userName,
        "uidNumber": default(uidNumber),
        "gidNumber": default(gidNumber, 100),
        "loginShell": default(loginShell, "/bin/bash"),
        "homeDirectory": default(unixHomeDirectory, "/home/%(userName)s"),
        "sn": familyName,
        "givenName": givenName,
        "userPassword": password,
        "mail": email,
        "description": default(description),
        "displayName": default(displayName, "%(familyName)s %(givenName)s"),
    }


    Group = {
        "objectClass": [
            "top",
            "posixGroup",
            ],
        "cn": groupName,
        "gidNumber": default(gidNumber),
        "description": default(description),
    }

このテンプレートファイルの特徴は以下です。

* *objectClass* に要素すべてが str 型の list を格納しています

    - バックエンドに属性を渡す際、必ず objectClass がここで定義した属性になります

* User の *uid* には userName という Unicorn ID Manager のフロントエンド属性が割り当てられています

    - Unicorn ID Manager から **userName=user1** としてユーザー登録を行った時、
      バックエンドには **uid: user1** として登録されます

* User の *mail* には email という Unicorn ID Manager の任意属性が割り当てられています

    - ルールは *uid* と同様ですが、もし email に複数の値が格納されていた場合、
      バックエンドの mail 属性にも同様に複数の値が割り当てられます

* User の *loginShell* には default 関数の返り値が割り当てられています

    - default 関数の第一引数 loginShell が指定されなかった場合、第二引数の
      /bin/bash が loginShell に割り当てられます

* User の *homeDirectory* には default 関数の返り値が割り当てられています

    - default 関数の第一引数 homeDirectory が指定されなかった場合、第二引数の
      /home/%(userName)s が割り当てられます。なお、 %(userName)s は Unicorn ID Manager
      の任意属性 userName の値に展開されます

* Group の *description* には default 関数の返り値が割り当てられています

    - default 関数の第一引数 description が指定されなかった場合、 *description*
      は空になります

フロントエンド属性とバックエンド属性
-------------------------------------

Unicorn ID Manager は複数の種類の異なるバックエンドを扱うために、
*フロントエンド属性* と *バックエンド属性* を定義しています。

*フロントエンド属性* は管理者が任意に定義する属性です。普段の運用では主に、
この *フロントエンド属性* を見てユーザーやグループの管理を行います。

*バックエンド属性* はバックエンド固有の属性です。たとえば、バックエンドタイプが
LDAP の場合を考えます。 LDAP にはスキーマがあり、登録可能な属性が決まっています。
posixAccount という objectClass のスキーマの場合、必ず、 uidNumber, homeDirectory,
loginShell などの決まった属性が必要になります。このようなバックエンドに固有の属性を
Unicorn ID Manager はバックエンド属性として扱います。

管理者は Unicorn ID Manager の設定時に、 *フロントエンド属性* と *バックエンド属性*
を関連付けます。その関連付けた設定ファイルがバックエンドのテンプレートファイルとなります。
テンプレートファイルで *フロントエンド属性* にあたるのが、テンプレートファイルの
Python dict オブジェクト内の値に存在する識別子です
(以下の場合、 userName と familyName 、 givenName が識別子です。
124 と True はそれぞれ数値と bool 値です) 。

displayName 属性には str 型の値が割り当てられていますが、その中に **%(...)s** という形式の
プレースホルダーが含まれています。このプレースホルダーの中に指定された識別子も、
*フロントエンド属性* になります。

.. code-block:: python

    User = {
        "cn": userName,
        "gidNumber": 124,
        "active": True,
        "displayName": "%(familyName)s %(givenName)s",
        ...

それぞれのテンプレートファイルで共通の *フロントエンド属性* を定義しておくことで、
ID の一元管理が可能です (以下は例です) 。

::

    フロントエンド属性     バックエンド属性

    userName   ------+---> uid (LDAP)
                     |
                     +---> cn (AD)
                     |
                     +---> userPrincipalName (Azure)

    familyName ------+---> sn (LDAP)
                     |
                     +---> sn (AD)
                     |
                     +---> surname (Azure)
                     |
                     +---> familyName (Google)


特別なフロントエンド属性
-------------------------

*フロントエンド属性* は任意に定義可能ですが、以下に挙げる *フロントエンド属性*
は Unicorn ID Manager で特別な意味を持つものです。

* userName
* groupName
* password
* active
* member

userName
^^^^^^^^^

ターゲット内において、ユーザーを一意に識別するためのフロントエンド属性です。
ユーザーを一意に識別するために別の属性を定義することはできません。

groupName
^^^^^^^^^^

ターゲット内において、グループを一意に識別するためのフロントエンド属性です。
グループを一意に識別するために別の属性を定義することはできません。

password
^^^^^^^^^

ユーザーのパスワードを示すフロントエンド属性です。
他の属性名をパスワードを示すフロントエンド属性にすることはできません。
また、 password 属性はオプション属性として定義しなくてもユーザー登録時に
省略可能です。省略した場合、ターゲットの設定で定めたポリシーに基づいて
ランダムな文字列が割り当てられます。ランダムに生成されたパスワードは
:doc:`unicornidm-tool` や管理画面から確認できます。

active
^^^^^^^

ユーザーの有効・無効を示すフロントエンド属性です。 True の場合は有効を示し、
False の場合は無効を示します。ユーザーの有効・無効を示すために別の属性を
定義することはできません。

member
^^^^^^^

グループのメンバーを示す属性です。グループのメンバーを示すために別の属性を
定義することはできません。

フロントエンド属性の制限事項
--------------------------------

*フロントエンド属性* は以下の制限事項があります。

* 大文字と小文字を区別される
* フロントエンド属性に **-** (0x2D) を含むことはできない


テンプレートファイル内の関数について
--------------------------------------

.. versionadded:: 3.2.14

テンプレートファイル内では、上述で紹介している default 関数以外の関数も
利用可能です。

たとえば、以下をご覧ください。

::

    User = {
        "name": validate_length(userName, 8, 16),
        "familyName": familyName,
        "givenName": givenName,
        "password": password,
        "mail": validate_mail(email),
    }

この定義において、 *userName* というフロントエンド属性の値は、
8 文字以上 16 文字以下の文字列かどうかを判定します。
この条件に合わない文字列の場合、この定義のバックエンドの処理は
エラーとなります (バックエンドへの接続が行われる前にエラーとなります) 。

同様に、 *mail* というフロントエンド属性も、メールアドレス形式ではない
場合に、エラーとなります。

このように、 default 関数以外の属性もテンプレートファイル内で利用することが
可能です。

ビルトイン関数
^^^^^^^^^^^^^^^

テンプレートファイルで利用可能な関数は Unicorn ID Manager に予め
用意されており、以下の関数が利用可能です。

.. py:function :: default(attr, alternative=None)

    フロントエンド属性 ``attr`` のデフォルト値 ``alternative`` を指定します。

    リソース追加操作時にフロントエンド属性 ``attr`` の指定がない場合に、 ``alternative`` の値を対応するバックエンド属性の値とします。
    リソース **更新** 時に ``attr`` が与えられなかった場合には、対応するバックエンド属性は更新されません。

    第二引数 ``alternative`` を与えなかった場合（つまり ``default(attr)`` と書いた場合）には、
    対応するバックエンド属性は空になります。

.. py:function :: default_hidden(attr, alternative=None)

    フロントエンド属性 *attr* の指定がない場合に、
    *alternative* の値を適用します。
    *alternative* が None の場合、バックエンド属性は空になります。
    この関数で指定した属性はブラウザーやコマンドラインツールから
    ユーザーやグループを参照しても表示されません。この関数は、
    初期値を指定して、その属性が更新されないようにしたい場合に利用します。

.. py:function :: validate_length(attr, min_len=None, max_len=None)

    フロントエンド属性 *attr* の長さが、 *min_len* 以上、 *max_len* 以下
    かどうかを判定し、この条件に一致しない場合エラーを発生させます。
    *min_len* が None の場合、文字列の下限はチェックされません。
    *max_len* が None の場合、文字列の上限はチェックされません。

.. py:function :: validate_mail(attr)

    フロントエンド属性 *attr* の文字列がメールアドレス形式かどうかを
    判定し、この条件に一致しない場合エラーを発生させます。

.. py:function :: validate_number(attr)

    フロントエンド属性 *attr* の文字列が数字かどうかを
    判定し、この条件に一致しない場合エラーを発生させます。

    .. versionadded:: 3.7.11

.. py:function :: validate_regex(attr, pattern)

    フロントエンド属性 *attr* の文字列が正規表現 *pattern* に合致するかどうかを
    判定し、この条件に一致しない場合エラーを発生させます。
    *pattern* は Python の
    `re <https://docs.python.org/3/library/re.html#regular-expression-syntax>`_ モジュール
    の構文に基づく文字列です。たとえば、 *pattern* に **r"[a-z1-9]{4}"** と指定すれば、
    英小文字と 1 から 9 までの数字で構成される 4 文字の文字列に合致しない場合エラーとなります。

    .. versionadded:: 3.7.11

ユーザー定義関数
^^^^^^^^^^^^^^^^^

ビルトイン関数のみで要件を満たさない場合、任意に定義した関数
(ユーザー定義関数) を利用することができます。

ユーザー定義関数は以下のステップで作成することができます。

1. ``/opt/osstech/lib/unicornidm/unicornidm/template_func/`` ディレクトリ以下に
   任意の名前の Python 形式のファイルを作成
   (ファイル名末尾は ``.py`` にする必要があります)

2. 作成したファイル内に任意の名前の関数を定義

3. テンプレートファイル内で作成した関数を指定し、 Unicorn ID Manager
   を再起動

定義する関数は、以下のルールで作成してください。

* 第一引数にフロントエンド属性の変数がセットされることを想定すること

* 第一引数の値は list 型か None が指定されることを想定すること

* 第一引数に None が指定された場合、 None を返すこと

* 第一引数が list 型の場合、 list 型を返すこと

* 値の妥当性評価を行う関数の場合、妥当ではない場合 ValidationError を返すこと

* 値の妥当性評価を行う関数で、値が妥当な場合、その値をそのまま返すこと

以下は与えられた値の先頭に必ず第二引数で指定した文字列を付加する関数と、
文字列の先頭が abc で始まるかどうかを判定する関数の例です。

.. code-block:: python

    from . import ValidationError


    def add_prefix(value, prefix):
        if value is None:
            return None
        return ['%s%s' % (prefix, x) for x in value]


    def must_start_with_abc(value):
        if value is None:
            return None
        for v in value:
            if not v.startswith('abc'):
                raise ValidationError('Must start with "abc"')
        return value

これを以下のようにテンプレートファイルで利用することができます。

::

    User = {
        "name": add_prefix(userName, 'PP:'),
        "employeeNumber": must_start_with_abc(employeeNumber),
    }

*userName* が "kamei" 、の場合 バックエンドに渡される *name* 属性は
"PP:kamei" となります。また、 *employeeNumber* が "def123" の場合、
エラーとなります。
