コンテンツにスキップ

ユーザ取得(選択)

概要

APIを使用してユーザの内容を取得することができます。

事前準備

APIの操作を行う前にAPIキーの作成を実施してください。

リクエスト

下記のリクエスト形式で、jsonデータを送信します。

設定項目 値
HTTPメソッド POST
Content-Type application/json
文字コード UTF-8
URL http://{サーバ名}/api/users/get (※1)
Body 以下のjsonデータを参考のこと。詳細は「JSONデータレイアウト:共通」、「JSONデータレイアウト:View」を参照してください。

(※1){サーバ名}の部分は、適宜、環境に合わせて編集してください。
  pleasanter.netの場合は以下の形式になります。
  https://pleasanter.net/fs/api/users/get

(a)ユーザIDで特定のユーザ情報を取得する場合

JSON

{
    "ApiVersion": 1.1,
    "ApiKey": "sad610bHDo04720DoloA356...",
    "View": {
        "ColumnFilterHash": {
            "UserId": "[11,12]"
        }
    }
}

UserIdによるフィルタは、複数の値を指定可能な配列となっておりますので上記のように記述してください。

(b)条件を指定して一致するユーザ情報を取得する場合

JSON
{
    "ApiVersion": 1.1,
    "ApiKey": "1ao3ea5kDH2335sar529UY...",
    "View": {
        "ColumnFilterHash": {
            "DeptId": "[7,8]",
            "Groups": "[1,2]",
            "Gender": "[2]"
        }
    }
}

レスポンス

下記の形式のjsonデータが返却されます。データのレイアウトは「JSONデータレイアウト:User」を参照してください(パスワードは取得できません)。
また、拡張項目(ClassHash/NumHash/DateHash/DescriptionHash/CheckHash)を含めて取得できます。取得できる項目の詳細は「拡張項目」を参照してください。

以下の項目はテナント管理者のAPIキーで実行した場合のみ出力されます。

出力項目 画面上の表示名
LastLoginTime 最終ログイン日時
PasswordExpirationTime パスワード有効期限
PasswordChangeTime パスワード変更日時
NumberOfLogins ログイン回数
NumberOfDenial ログイン失敗回数
TenantManager テナント管理者
Disabled 無効
Lockout ロック
LockoutCounter ロックカウンター

(a) テナント管理者のAPIキーで実行した場合

JSON
{
    "StatusCode": 200,
    "Response": {
        "Offset": 0,
        "PageSize": 200,
        "TotalCount": 1,
        "Data": [
            {
                "TenantId": 12345,
                "UserId": 12345,
                "Ver": 1,
                "LoginId": "hayato",
                "GlobalId": "",
                "Name": "中野 隼人",
                "UserCode": "",
                "Birthday": "2016-03-27T00:00:00",
                "Gender": "",
                "Language": "ja",
                "TimeZone": "Tokyo Standard Time",
                "DeptCode": "",
                "DeptId": 0,
                "Theme": "",
                "Body": "",
                "LastLoginTime": "2023-08-17T12:00:00",
                "PasswordExpirationTime": "2023-08-31T12:00:00",
                "PasswordChangeTime": "2023-06-02T12:00:00",
                "NumberOfLogins": 100,
                "NumberOfDenial": 5,
                "TenantManager": false,
                "Disabled": false,
                "Lockout": false,
                "LockoutCounter": 0,
                "UserSettings": "{}",
                "SecondaryAuthenticationCode": "",
                "SecondaryAuthenticationCodeExpirationTime": "1899-12-30T00:00:00",
                "LdapSearchRoot": "",
                "SynchronizedTime": "1899-12-30T00:00:00",
                "Comments": "[]",
                "Creator": 2,
                "Updator": 1,
                "CreatedTime": "2023-04-01T12:00:00",
                "UpdatedTime": "2023-08-15T12:00:00",
                "MailAddresses": [
                    "webmaster@example.com",
                    "info@example.com"
                ],
                "ApiVersion": 1.1,
                "ClassHash": {
                },
                "NumHash": {
                },
                "DateHash": {
                },
                "DescriptionHash": {
                },
                "CheckHash": {
                },
                "AttachmentsHash": {
                }
            }
        ]
    }
}

(b) テナント管理者以外のAPIキーで実行した場合

JSON
{
    "StatusCode": 200,
    "Response": {
        "Offset": 0,
        "PageSize": 200,
        "TotalCount": 1,
        "Data": [
            {
                "TenantId": 12345,
                "UserId": 12345,
                "Ver": 1,
                "LoginId": "hayato",
                "GlobalId": "",
                "Name": "中野 隼人",
                "UserCode": "",
                "Birthday": "2016-03-27T00:00:00",
                "Gender": "",
                "Language": "ja",
                "TimeZone": "Tokyo Standard Time",
                "DeptCode": "",
                "DeptId": 0,
                "Theme": "",
                "Body": "",
                "UserSettings": "{}",
                "SecondaryAuthenticationCode": "",
                "SecondaryAuthenticationCodeExpirationTime": "1899-12-30T00:00:00",
                "LdapSearchRoot": "",
                "SynchronizedTime": "1899-12-30T00:00:00",
                "Comments": "[]",
                "Creator": 2,
                "Updator": 1,
                "CreatedTime": "2023-04-01T12:00:00",
                "UpdatedTime": "2023-08-15T12:00:00",
                "MailAddresses": [
                    "webmaster@example.com",
                    "info@example.com"
                ],
                "ApiVersion": 1.1,
                "ClassHash": {
                },
                "NumHash": {
                },
                "DateHash": {
                },
                "DescriptionHash": {
                },
                "CheckHash": {
                },
                "AttachmentsHash": {
                }
            }
        ]
    }
}

サンプルコード

Note

コード内の【 ... 】は適宜修正してください。

1. 未ログインユーザを棚卸表へ抽出する

テナント管理者のAPIキーで全ユーザを取得し、長期間ログインの無いユーザ(一度もログインしていない場合はアカウント作成日時を基準)だけを棚卸テーブルへ抽出・書き込みします。
棚卸テーブル側の標準通知設定と組み合わせることで、新たに棚卸対象となったユーザが発生したときだけ自動でメール通知できます。

実務での使いどころ

ユーザアカウントの利用状況棚卸(アカウント整理・セキュリティ点検)にそのまま使えます。全ユーザを毎回洗い出す必要がなく、一定期間ログインの無いユーザだけを自動的に抽出するため、担当者は棚卸テーブルを確認するだけで対応漏れを防げます。
棚卸テーブル側で「作成後」のみ通知を有効化しておけば、新たに棚卸対象になったユーザが発生した時点でメールが届く一方、既に棚卸済み(対応中/完了等)のユーザがバッチ実行のたびに再通知されることはありません。
状況項目はスクリプト側から送信しないため、担当者がつけた対応状況もそのまま保持されます。
アカウント作成日時も常に記録するため、未ログインユーザの経過日数がどの日付を根拠に算出されたかを後から確認できます。

事前準備

  1. 棚卸テーブル(書き込み先のテーブル)に下記の項目を有効化します。
    • 分類B(項目コード:ClassB):ログインIDを保持する「ログインID」欄(突合・重複判定のKeyとして使用)
    • 数値A(項目コード:NumA):「経過日数」欄
    • 日付A(項目コード:DateA):「最終ログイン日時」欄(未ログインユーザは空欄のまま)
    • 日付B(項目コード:DateB):「同期日時」欄
    • 日付C(項目コード:DateC):「アカウント作成日時」欄(経過日数の算出根拠を後から検証するための項目)
  2. 棚卸テーブルの通知を設定します(新規棚卸対象の発生時のみメール通知するため)。
    1. 「テーブルの管理 > 通知」タブで「新規作成」を選択します。
    2. 通知種別: メール
    3. アドレス: 通知を受け取りたいメールアドレス
    4. 「作成後」のみチェックを入れ、「更新後」「削除後」「コピー後」「一括更新後」「一括削除後」「インポート後」はチェックを外します(これにより、既に棚卸済みのユーザがバッチ実行のたびに再通知されることを防げます)。
  3. スクリプトを設定します。
    1. 下記サンプルコードの BASE_URL・API_KEY・OUTPUT_SITE_ID を環境に合わせて書き換えます。
    2. API_KEY は必ずテナント管理者権限のユーザで発行したものを使用します(LastLoginTime の取得に必要です)。
    3. 必要に応じて INACTIVE_DAYS(棚卸対象とする経過日数。既定90日)を調整します。
    4. 定期的に棚卸を行いたい場合は、タスクスケジューラ等で本スクリプトを定期実行するよう設定します。
Python
import datetime
import requests

# ===== 設定 =====
# 【重要】API_KEYは必ずテナント管理者権限のユーザーで発行したものを使用してください。
# 一般ユーザーのAPIキーでは、LastLoginTime等の項目が取得できず、正しく動作しません。
BASE_URL = "【BASE_URL】"
API_KEY = "【APIKEY】"
OUTPUT_SITE_ID = "【OUTPUT_SITE_ID】"

PAGE_SIZE = 200          # ユーザー取得時の1ページあたりの件数
BULK_UPSERT_MAX = 10000  # bulkupsertで1回に送れる件数の上限(General.jsonの既定値。環境により変更されている場合あり)

INACTIVE_DAYS = 90  # 何日以上ログインが無ければ棚卸対象とするか

LOGIN_ID_COLUMN = "ClassB"        # ログインID(Keyとして使用。重複判定・突合のキー)
LAST_LOGIN_COLUMN = "DateA"       # 最終ログイン日時(未ログインの場合は空欄のまま)
ELAPSED_DAYS_COLUMN = "NumA"      # 経過日数(最終ログインから、または未ログインならアカウント作成日時からの日数)
SYNCED_AT_COLUMN = "DateB"        # 棚卸実施日時(画面上のラベルは「同期日時」)
ACCOUNT_CREATED_COLUMN = "DateC"  # アカウント作成日時(経過日数の算出根拠を後から検証できるよう、常に書き込む)

# Pleasanterにおける「未ログイン」を表す内部的な固定値(実機確認済み。バージョンアップで変わる可能性あり)
NEVER_LOGGED_IN_SENTINEL = datetime.datetime(1899, 12, 30)


def api_post(url, payload):
    """Pleasanter APIへのPOSTリクエストを実行する共通処理"""
    response = requests.post(
        url,
        json=payload,
        headers={"Content-Type": "application/json"}
    )
    response.raise_for_status()
    return response


def fetch_all_users():
    """全ユーザーを、取得件数に応じてoffsetを進めながら取得する(テナント管理者のAPIキーが必要)"""
    users = []
    offset = 0

    while True:
        payload = {
            "ApiVersion": 1.1,
            "ApiKey": API_KEY,
            "Offset": offset,
            "PageSize": PAGE_SIZE,
        }
        body = api_post(f"{BASE_URL}/api/users/get", payload).json()

        page = body["Response"]["Data"]
        users.extend(page)

        offset += len(page)  # 固定値ではなく、取得件数に応じて進める
        total_count = body["Response"]["TotalCount"]

        if len(page) == 0 or offset >= total_count:
            return users


def parse_last_login(last_login_str):
    """未ログインを表す特殊値をNoneに変換し、それ以外はdatetimeで返す"""
    last_login_datetime = datetime.datetime.fromisoformat(last_login_str)

    if last_login_datetime == NEVER_LOGGED_IN_SENTINEL:
        return None

    return last_login_datetime


def elapsed_days_since_active(user, now):
    """経過日数を算出する。最終ログイン日時があればそこから、
    一度もログインしていない場合はアカウント作成日時(CreatedTime)からの日数にフォールバックする。
    アカウント作成日時は、経過日数の算出根拠を後から検証できるよう、ログイン有無に関わらず返す。
    戻り値は (経過日数, 最終ログイン日時 or None, アカウント作成日時)。"""
    last_login = parse_last_login(user.get("LastLoginTime"))
    created_time = datetime.datetime.fromisoformat(user["CreatedTime"])

    if last_login is not None:
        return (now - last_login).days, last_login, created_time

    return (now - created_time).days, None, created_time


def build_bulk_data(users, now):
    """棚卸対象(経過日数がINACTIVE_DAYS以上)のユーザーのみを、bulkupsert用のData配列に変換する
    (状況項目は含めない=担当者がつけた対応状況を保持する)"""
    data = []

    for user in users:
        if user.get("TenantManager"):
            continue  # テナント管理者は対象外

        elapsed_days, last_login, created_time = elapsed_days_since_active(user, now)
        if elapsed_days < INACTIVE_DAYS:
            continue  # 直近ログインがある(=アクティブな)ユーザーは棚卸対象外

        title = user["Name"] if last_login is not None else f"{user['Name']}(未ログイン)"

        date_hash = {
            SYNCED_AT_COLUMN: now.isoformat(),
            ACCOUNT_CREATED_COLUMN: created_time.isoformat(),
        }
        if last_login is not None:
            date_hash[LAST_LOGIN_COLUMN] = last_login.isoformat()

        data.append({
            "Title": title,
            "ClassHash": {
                LOGIN_ID_COLUMN: user["LoginId"]
            },
            "NumHash": {
                ELAPSED_DAYS_COLUMN: elapsed_days
            },
            "DateHash": date_hash,
        })

    return data


def chunked(items, size):
    """リストを指定件数ごとに分割する"""
    for i in range(0, len(items), size):
        yield items[i:i + size]


def bulk_upsert_records(data_chunk):
    """ログインIDをキーに、レコードをまとめて作成・更新する(1回分)"""
    payload = {
        "ApiVersion": 1.1,
        "ApiKey": API_KEY,
        "Keys": [LOGIN_ID_COLUMN],       # このキーで一致するレコードは更新、無ければ新規作成
        "KeyNotFoundCreate": True,
        "Data": data_chunk,
    }
    response = api_post(f"{BASE_URL}/api/items/{OUTPUT_SITE_ID}/bulkupsert", payload)
    return response.json()


def main():
    """長期間ログインの無いユーザーを抽出し、Pleasanterの棚卸テーブルへ一括で書き込む。
    状況項目はペイロードに含めないため、担当者が手動でつけた対応状況(対応中/完了等)は保持される。
    BulkUpsertMaxを超える件数がある場合は、BULK_UPSERT_MAXごとに分割して送信する。
    棚卸テーブル側で標準通知(作成後のみ)を設定しておくと、新規に棚卸対象となったユーザーの
    発生だけが自動的にメール通知される。"""
    now = datetime.datetime.now()

    all_users = fetch_all_users()
    bulk_data = build_bulk_data(all_users, now)

    if not bulk_data:
        print(f"(info) {INACTIVE_DAYS}日以上ログインの無いユーザーは見つかりませんでした。")
        return

    success_count = 0
    error_count = 0

    for chunk_index, data_chunk in enumerate(chunked(bulk_data, BULK_UPSERT_MAX), start=1):
        result = bulk_upsert_records(data_chunk)
        status_code = result.get("StatusCode")
        message = result.get("Message")

        print(f"[{chunk_index}回目の送信] StatusCode={status_code} Message={message}")

        if status_code != 200:
            error_count += 1
            print(f"[{chunk_index}回目の送信] エラーが発生したため、これ以降のユーザーは反映されていない可能性があります。")
        else:
            success_count += 1

    print(f"完了:棚卸対象={len(bulk_data)}件 成功した送信={success_count}回 エラーが起きた送信={error_count}回")


if __name__ == "__main__":
    main()

エラー時の確認事項

・API使用時の注意点やエラーが発生する場合の確認事項
・FAQ:変更後の設定ファイルやAPIリクエスト(JSON形式)が正しく認識されない場合の確認事項

仕様変更について

※ 2018年11月よりAPIの仕様が一部変更となりました。
- URLの形式が '/pleasanter/api_items/xxxx' から '/pleasanter/api/items/xxxx' に変更されました。
- Content-Type の指定が'application/x-www-form-urlencoded' から 'application/json'に変更されました。