コマンドバックエンド
=====================

概要
-----

::

    unicornidm.conf

説明
-----

バックエンドセクションのうち、 `type` が `command` となっているバックエンドは、
ユーザー追加、更新などの各種操作と共に実行するコマンドの設定を行います。

Unicorn ID Manager v3 ではこのコマンドのことを **コマンドバックエンド** と呼びます。
これは LDAPバックエンドなどその他のバックエンドと同じように動作します。

.. _basic-command-setting:

リファレンス
------------

unicornidm.conf のパラメーター
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. envvar:: type

    バックエンドのタイプを指定します。 `command` に設定してください。

.. envvar:: user_add_command

    ユーザー登録の際の追加コマンドを指定します。

.. envvar:: user_modify_command

    ユーザー更新の際の追加コマンドを指定します。

.. envvar:: user_enable_command

    ユーザー有効化の際の追加コマンドを指定します。

.. envvar:: user_disable_command

    ユーザー無効化の際の追加コマンドを指定します。

.. envvar:: user_change_password_command

    ユーザーのパスワード変更の際の追加コマンドを指定します。

.. envvar:: user_delete_command

    ユーザー削除の際の追加コマンドを指定します。

.. envvar:: user_rename_command

    ユーザー名のリネームの際の追加コマンドを指定します。

.. envvar:: group_add_command

    グループ登録の際の追加コマンドを指定します。

.. envvar:: group_modify_command

    グループ更新の際の追加コマンドを指定します。

.. envvar:: group_delete_command

    グループ削除の際の追加コマンドを指定します。

.. envvar:: group_rename_command

    グループ名のリネームの際の追加コマンドを指定します。

.. envvar:: group_add_members_command

    グループにメンバーを追加する際の追加コマンドを指定します。

.. envvar:: group_delete_members_command

    グループからメンバーを削除する際の追加コマンドを指定します。

テンプレートファイル
^^^^^^^^^^^^^^^^^^^^

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

::

User = {
    "userName": userName,
    "mail": mail,
    "password": password,
    "homeDirectory": default(unixHomeDirectory, "/home/%(userName)s"),
    "gidNumber": default(gidNumber, 100),
}


Group = { 
    "groupName": groupName,
}


基本的な設定
------------

ユーザー追加時だけコマンドを実行させるための設定手順を説明します。

1. 実行したいコマンドを Unicorn ID Manager が動作しているサーバーの適当なディレクトリに置いて、 :ref:`uwsgi.conf` の `uid`/`gid` パラメータで指定したユーザー・グループで実行できるようにしてください。

2. :doc:`../unicornidm-conf` に以下のように設定してください。

::

    [backend:<コマンドバックエンドの名前>]
    type = command
    user_add_command = <手順1 で設定したコマンドへの絶対パス> something {name}

3. テンプレートファイル :file:`templates/<コマンドバックエンドの名前>.py` を作成し、以下のように書いてください。
   これはRPMパッケージをインストールすると作成される :file:`templates/command-example.py` の中身と同じです。

::

    User = {
        "userName": userName,
        "mail": mail,
        "password": password,
        "homeDirectory": unixHomeDirectory,
    }


    Group = {
        "groupName": groupName,
    }

.. note::

    `"userName": userName,` と `"groupName": groupName,` の行は変更しないでください。


上記バックエンドをターゲットに紐付けると、そのターゲットからの操作でコマンドが実行されます。

この設定により、コマンドバックエンド以外の全てのバックエンドでユーザー登録操作が成功すると、設定したコマンドが実行されるようになります。

上記設定の場合、コマンドの第一引数に `something` が、 第二引数に追加したユーザーの `userName` フロントエンド属性の値が渡されます。
他のユーザーの属性情報は、手順3 で設定した属性のみがコマンドの標準入力に JSON として渡されます。そのフォーマットについては :ref:`command-json-format` を参照してください。

他の操作で実行したいコマンドについても同様の方法で設定できます。
グループへの操作に対しても同様です。

動作詳細
--------

コマンド実行タイミング
^^^^^^^^^^^^^^^^^^^^^^

設定したコマンドは指定した操作が1回実行される度に1度実行されます。

**結果詳細画面で見える表の1行分が1回の操作にあたります。**
例えばCSVファイルのアップロードによる一括処理はおおよそCSVの1行が1回の操作になります。
ユーザー有効化操作は確実にCSVの1行が1操作になります。
Web UI からのユーザー有効化操作は1回ボタンを押して実行するとそれが1回分の操作になります。
逆にユーザー追加処理はユーザー追加操作1回とグループへのメンバー追加操作複数回に分かれる場合があります。
この場合ユーザー追加に設定したコマンドが1回実行され、メンバー追加操作に設定したコマンドが複数回実行されます。

コマンドはコマンドバックエンド以外の全てのバックエンドでの操作が完了した後に実行されます。
これは :doc:`../unicornidm-conf` でどの順番でバックエンドを書いたかに依らずこの挙動になります。

コマンドを実行するかどうかはそれら先に実行が終わったバックエンドの結果を元に決まります。
現状の仕様ではコマンドが実行されるのはそれらバックエンド全てで操作が成功した場合だけです。
一つでもバックエンドが操作に失敗するとコマンドは実行されません。

Unicorn ID Manager の管理者アカウントは操作実行前にコマンドバックエンドを実行するかどうかを選択することができます。
ブラウザの場合は操作実行前の画面に表示されるバックエンド選択チェックボックスで選択できます。
:doc:`../unicornidm-tool` ではオプションで選択できます。

一般ユーザーにはコマンド実行の選択権はありません。

コマンドの成功・失敗
^^^^^^^^^^^^^^^^^^^^

コマンドバックエンドにも他のバックエンドと同じように操作の成功・失敗の概念があります。
コマンドバックエンドの操作の成否はブラウザの結果画面や :doc:`../unicornidm-tool` で他のバックエンドと同じように確認することができます。

コマンドが `0` 以外のステータスコードで終了した場合、そのコマンドは失敗したと扱われます。
ステータスコード `0` で終了した場合は成功とみなされます。

失敗したコマンドの標準エラー出力は操作結果詳細画面でエラーメッセージとして表示されます。
対して成功した場合には操作結果詳細画面には何も表示されません。
コマンドの成否に関わらず、標準出力と標準エラー出力は :any:`unicornidm.log` に出力されます。
これら出力は全て UTF-8 でデコードされて表示されます。


コマンドの文法
^^^^^^^^^^^^^^

コマンドは :doc:`../unicornidm-conf` に1行の文字列として設定します。
例えば以下のように設定します。

::
    
    user_delete_command = rm -rf /home/osstech/student\ directory/{name}


コマンドはシェル上で実行するコマンドのように字句解析（文字列分割）され、実行されます。
この字句解析は Python の標準ライブラリである shlex を使用しています。

.. attention::
    実際にシェルを経由して実行しているわけではないので、シェルの機能が使えるわけではありません。
    例えばパイプやリダイレクションは利用できません。
    コマンドとして設定した実行ファイルが直接実行されます。

字句解析の結果の最初の文字列が 実行するコマンドのファイル名として実行されます。
上記例の場合 `rm` が実行ファイル名だとみなされ、コマンドの残りの部分は引数として `rm` へ渡されます。
もちろん `PATH` 環境変数などは考慮されます。
この実行ファイルは Unicorn ID Manager が動いているサーバー上になければなりません。

.. hint::
    他のサーバー (UNIX, Windows どちらでも）にあるコマンドを実行したい場合はご相談ください。

.. _command-attribute-expansion:

コマンドライン中の属性展開
""""""""""""""""""""""""""

コマンドの中の `{属性名}` （上記例の `{name}` など）はそのバックエンド属性の値に変換されます。
この変換は上記のシェルに似た字句解析の後に行われます。
使用できる属性名は操作によって変わります。

唯一 `name` という属性名はどの操作でも同じ意味で使用できます。
これはユーザーの場合 `userName` フロントエンド属性の、グループの場合 `groupName` フロントエンド属性の値に変換されます。

.. deprecated:: 4.0.0

    `userName`, `groupName` は環境変数から取得するようにしてください。
    それぞれ、 `UIDM_USERNAME`, `UIDM_GROUPNAME` という環境変数名で渡されます。
    :ref:`command-environment-variables` を参照してください。


.. _command-json-format:

コマンドの標準入力に渡される JSON
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

コマンドには標準入力から JSON 形式で操作対象ユーザー/グループの情報が渡されます。
渡される情報は操作によって変わります。

* 追加操作の場合

  追加するユーザー/グループの属性が、テンプレートファイルで定義した形式で渡されます。
  ただし、 `userName`, `groupName` 属性だけは取り除かれます。
  JSON の構造はJSONオブジェクトのキーに属性ID が、値部にその属性の値が文字列の配列として渡されます。
  オブジェクトのネストはなく、フラットな key-value 型のJSONオブジェクトになります。

  例えば :ref:`basic-command-setting` のテンプレートファイルだと、以下のような JSON が渡されます。

    .. code-block:: json

        {
            "mail": ["user1@example.com"],
            "password": ["weak-password"],
            "homeDirectory": ["/home/user1"],
        }

* 更新操作の場合

  更新対象の属性 **のみ** が渡されます。
  つまり、ブラウザ操作による更新では画面に映った属性の中で値を修正したものだけが渡されます。
  CSV による更新操作ではCSVのヘッダーに書いた属性だけが渡されます。（ただし `userName`, `groupName` は渡されません）

  JSON の構造は追加のときと同様、属性ID がJSONオブジェクトのキーになりますが、値の部分の構造が追加のときと違います。

  例えば、以下のような JSON が渡されます。

    .. code-block:: json

        {
            "mail": {
                "add": ["user1+alias1@example.com"],
                "delete": ["user1@example.com"]
            },
            "homeDirectory": {
                "add": ["/home/bjensen"],
                "delete": -1
            }
        }

  上の例は `mail` 属性も `homeDirectory` 属性も更新しようとした場合です。
  例えば、 `mail` 属性しか更新しようとしなかった場合には、上記 JSON から `homeDirectory` のフィールドがなくなったものが渡されます。

  値の部分の構造は以下のようになります。

    .. code-block:: json

        {
            "add": ["added-value1", "added-value2"],
            "delete": ["deleted-value1", "deleted-value2", "deleted-value3"]
        }


  `add` フィールドに追加しようとした値が、 `delete` フィールドに削除しようとした値が渡されます。
  追加、削除しようとした値がない場合には、対応するフィールド自体が渡されません。

  例（追加しようとした値がない場合）:

    .. code-block:: json

        {
            "delete": ["deleted-value1", "deleted-value2"],
        }

  例（削除しようとした値がない場合）:

    .. code-block:: json

        {
            "add": ["added-value1", "added-value2"],
        }


  CSV の `--` （ハイフン2つ）接頭辞を使った場合や、接頭辞を使わなかった場合は、どの属性内の値を削除しようとしているのかが分からないので、文字列のリストの代わりに `-1` が `delete` フィールドの値になります。

  例（`--` 接頭辞を使った場合）: 

    .. code-block:: json

        {
            "delete": -1
        }

  例（接頭辞を使わなかった場合）: 

    .. code-block:: json

        {
            "add": ["added-value1"],
            "delete": -1
        }

  ブラウザの画面操作で更新をした場合には、 Unicorn ID Manager の内部キャッシュとブラウザからのデータの差分を更新しようとするため、 `delete` に `-1` が来ることはありません。


* 削除操作の場合

  **削除前の** ユーザー/グループの属性が渡されます。
  構造は追加操作の場合と同じです。ただし、 `password` 属性は渡されません。

* 名前変更操作の場合

  新しい名前のみが、以下のような JSON が渡されます。

  ユーザーの名前変更の場合（ `bjensen` に名前を変更しようとした）:

    .. code-block:: json

        {
            "newUserName": "bjensen"
        }

  グループの名前変更の場合（ `group2` にグループ名を更新しようとした）:

    .. code-block:: json

        {
            "newGroupName": "group2"
        }


* パスワード変更操作の場合

  新しいパスワードのみが、以下のような JSON が渡されます。

    .. code-block:: json

        {
            "password": "new-P@ssw0rd"
        }

* ユーザー有効化/無効化操作の場合

  JSON は渡されません。標準入力には1バイトも渡りません。
  そのため、他の操作と同じようにコマンド側で JSON をパースしようとするとエラーになります（空文字列は JSON のフォーマットとは違うため）。

* メンバー追加/削除操作の場合

  追加/削除しようとしたメンバーの `userName` が渡されます。

  どちらの操作でも以下のような JSON になります。

    .. code-block:: json

        {
            "members": ["user1", "user2", "user3"]
        }

.. _command-environment-variables:

コマンドの環境変数から渡される情報
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

コマンドには環境変数で情報が渡されます。環境変数名には `UIDM_` という接頭辞が付きます。

以下の情報が渡されます。

.. envvar:: UIDM_OPERATION_NAME

   実行している操作の名前 (ex: `add`, `change_password`) が渡されます。

.. envvar:: UIDM_USERNAME

   ユーザーに対する操作のときのみ渡されます。
   操作対象ユーザーの `userName` が値になります。 

.. envvar:: UIDM_GROUPNAME

   グループに対する操作のときのみ渡されます。
   操作対象グループの `groupName` が値になります。 
