コンテンツにスキップ

サイト取得

概要

APIを使用してサイト情報を取得することができます。

制限事項

wikiには対応していません。

事前準備

APIの操作を行う前にAPIキーの作成を実施してください。また、この機能はテナント管理者でないと行えないため、ユーザ管理からテナント管理者の設定を行ってください。

リクエスト

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

設定項目 値
HTTPメソッド POST
Content-Type application/json
文字コード UTF-8
URL http://{サーバ名}/api/items/{サイトID}/getsite(※1)
Body 以下のjsonデータを参考のこと

(※1){サーバ名}、{サイトID}の部分は、適宜、環境に合わせて編集してください。
  pleasanter.netの場合は以下の形式になります。
  https://pleasanter.net/fs/api/items/{サイトID}/getsite
  {サイトID}には、取得対象のサイトIDを指定してください。

JSON
{
    "ApiVersion": "1.1",
    "ApiKey": "345yuAjA6789dA09d8uj6..."
}

レスポンス

下記の形式のJSONデータが返却されます。

JSON
{
    "StatusCode": 200,
    "Response": {
        "Data": {
            "TenantId": 1,
            "SiteId": 12345,
            "UpdatedTime": "2023-08-17T12:00:00",
            "Ver": 1,
            "Title": "記録テーブル",
            "Body": "",
            "SiteName": "",
            "SiteGroupName": "",
            "GridGuide": "",
            "EditorGuide": "",
            "CalendarGuide": "",
            "CrosstabGuide": "",
            "GanttGuide": "",
            "BurnDownGuide": "",
            "TimeSeriesGuide": "",
            "KambanGuide": "",
            "ImageLibGuide": "",
            "ReferenceType": "Results",
            "ParentId": 12344,
            "InheritPermission": 3301,
            "Permissions": [
            ],
            "SiteSettings": {
                "Version": 1.017,
                "ReferenceType": "Results",
                "EditorColumnHash": {
                    "General": [
                        "ResultId",
                        "Ver",
                        "Title",
                        "Body",
                        "Status",
                        "Manager",
                        "Owner",
                        "Comments",
                        "ClassA",
                        "ClassB",
                        "ClassC"
                    ]
                },
                "NoDisplayIfReadOnly": false
            },
            "Publish": false,
            "DisableCrossSearch": false,
            "LockedTime": "1899-12-30T00:00:00",
            "LockedUser": 0,
            "ApiCountDate": "1899-12-30T00:00:00",
            "ApiCount": 0,
            "Comments": "[]",
            "Creator": 1,
            "Updator": 1,
            "CreatedTime": "2023-08-17T09:00:00",
            "ApiVersion": 1.1,
            "ClassHash": {
            },
            "NumHash": {
            },
            "DateHash": {
            },
            "DescriptionHash": {
            },
            "CheckHash": {
            },
            "AttachmentsHash": {
            }
        }
    }
}

サンプルコード

Note

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

1. 複数のサイトのアクセス権を、1行1件のCSVに出力する

サイトID一覧(CSV)のサイトごとに、サイト取得APIでアクセス権を取得し、「どのサイトで、誰に、どの権限が付いているか」を1行1件のCSVに出力します。権限を継承しているサイトは、継承元のアクセス権を取得します。組織・グループ・ユーザーのIDは名前に変換し、権限の値はパターン名(読取専用・書き込み・リーダー・管理者・特殊)と9つの個別の権限に分けて出力します。

実務での使いどころ

監査への対応や、人事異動のあとの権限の棚卸に使えます。アクセス権は、サイトごとに「テーブルの管理」の「アクセス制御」を開かないと確認できず、複数のサイトをまとめて一覧にする機能はありません。継承しているサイトでは、継承元を開き直す必要もあります。出力したCSVをExcelで開けば、無効にした組織やユーザーに権限が残っていないかも確かめられます。

事前準備

1:棚卸したいサイトのIDを、1行目を見出しSiteIdにしたCSV(sites.csv、UTF-8)に書きます。

SiteId
887
869
3

2:サンプルコードのBASE_URL・API_KEYを書き替えてください。APIキーは、テナント管理者のものを使います。

Python
# ==========================================================
# アクセス権の棚卸:サイトID一覧(CSV)のサイトごとに、サイト取得APIでアクセス権を取得し、
# 「どのサイトで、誰に、どの権限が付いているか」を1行1件のCSVに出力します。
# 権限を継承しているサイトは、継承元のアクセス権を取得します。
# ==========================================================
import csv
from pathlib import Path

import requests

# ===== 設定 =====
# APIキーは、テナント管理者のものを使ってください(サイト取得APIの前提)
BASE_URL = "【URL】"
API_KEY = "【APIキー】"
INPUT_CSV = Path("./sites.csv")                  # 1行目は見出し「SiteId」
OUTPUT_CSV = Path("./site_permissions.csv")
PAGE_SIZE = 200

# 権限値はビットの和。標準のパターンは値が完全に一致するときのみ、その名前にする
PERMISSION_BITS = [
    (1, "読取り"), (2, "作成"), (4, "更新"), (8, "削除"), (16, "メール送信"),
    (32, "エクスポート"), (64, "インポート"), (128, "サイトの管理"), (256, "権限の管理"),
]
PATTERNS = {1: "読取専用", 31: "書き込み", 255: "リーダー", 511: "管理者"}
KIND_NAMES = {"Dept": "組織", "Group": "グループ", "User": "ユーザー"}
HEADER = (["サイトID", "タイトル", "継承元サイトID", "継承元タイトル", "対象種別", "対象ID", "対象名", "権限パターン"]
          + [name for _, name in PERMISSION_BITS] + ["備考"])


def api_post(path, payload=None):
    """Pleasanter APIへPOSTする。レスポンスのJSONを返す(HTTPエラーでも本文を返す)"""
    response = requests.post(
        f"{BASE_URL}{path}",
        json={"ApiVersion": 1.1, "ApiKey": API_KEY, **(payload or {})},
        headers={"Content-Type": "application/json"},
        timeout=60,
    )
    return response.status_code, response.json()


def fetch_all(path):
    """取得件数に応じてOffsetを進めながら、全件を取得する"""
    rows = []
    while True:
        _, body = api_post(path, {"Offset": len(rows), "PageSize": PAGE_SIZE})
        page = body["Response"]["Data"]
        rows.extend(page)
        if len(page) == 0 or len(rows) >= body["Response"]["TotalCount"]:
            return rows


def load_names():
    """組織・グループ・ユーザーの「ID → (名前, 無効か)」を、最初に1回だけ取得する"""
    return {
        "Dept": {d["DeptId"]: (d["DeptName"], d.get("Disabled")) for d in fetch_all("/api/depts/get")},
        "Group": {g["GroupId"]: (g["GroupName"], g.get("Disabled")) for g in fetch_all("/api/groups/get")},
        "User": {u["UserId"]: (u["Name"], u.get("Disabled")) for u in fetch_all("/api/users/get")},
    }


def get_site(site_id, cache):
    """サイトを取得する(同じサイトは1回のみ)。失敗したら (None, メッセージ) を返す"""
    if site_id not in cache:
        status, body = api_post(f"/api/items/{site_id}/getsite")
        cache[site_id] = (body["Response"]["Data"], "") if status == 200 else (None, body.get("Message", f"HTTP {status}"))
    return cache[site_id]


def read_site_ids(path):
    """入力のサイトIDを、重複を除いて読んだ順に返す"""
    with open(path, encoding="utf-8-sig", newline="") as f:
        ids = [int(row["SiteId"]) for row in csv.DictReader(f) if row["SiteId"].strip()]
    return list(dict.fromkeys(ids))


def permission_cells(value):
    """権限値を、パターン名と9つの個別権限(付いていれば〇)に分解する"""
    return [PATTERNS.get(value, "特殊")] + ["〇" if value & bit else "" for bit, _ in PERMISSION_BITS]


def site_rows(site_id, names, cache):
    """1サイト分の出力行を返す"""
    site, error = get_site(site_id, cache)
    if site is None:
        return [[site_id, "", "", "", "", "", "", ""] + [""] * len(PERMISSION_BITS) + [error]]
    # 継承しているサイトの Permissions は空になるため、継承元(継承の大元)のものを使う
    inherit_id = int(site["InheritPermission"])
    source, error = (site, "") if inherit_id == site_id else get_site(inherit_id, cache)
    if source is None:
        return [[site_id, site["Title"], inherit_id, "", "", "", "", ""] + [""] * len(PERMISSION_BITS) + [f"継承元を取得できません:{error}"]]
    head = [site_id, site["Title"], inherit_id, source["Title"]]
    permissions = source.get("Permissions") or []
    if not permissions:
        # 誰にも権限が付いていないサイトも、一覧で分かるように1行出す
        return [head + ["", "", "", ""] + [""] * len(PERMISSION_BITS) + ["アクセス権なし"]]
    rows = []
    for permission in permissions:
        kind, target_id, value = permission.split(",")
        name, disabled = names[kind].get(int(target_id), ("", False))
        rows.append(head + [KIND_NAMES.get(kind, kind), int(target_id), name]
                    + permission_cells(int(value)) + ["無効" if disabled else ""])
    return rows


def main():
    names = load_names()
    cache = {}
    rows = [row for site_id in read_site_ids(INPUT_CSV) for row in site_rows(site_id, names, cache)]
    # Excel でそのまま開けるように、BOM付きのUTF-8で書く
    with open(OUTPUT_CSV, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(HEADER)
        writer.writerows(rows)
    print(f"{len(rows)} 行を {OUTPUT_CSV} に出力しました。")


if __name__ == "__main__":
    main()

出力の例(site_permissions.csv)

サイトID,タイトル,継承元サイトID,継承元タイトル,対象種別,対象ID,対象名,権限パターン,読取り,作成,更新,削除,メール送信,エクスポート,インポート,サイトの管理,権限の管理,備考
887,案件管理,887,案件管理,組織,26,新設室,特殊,〇,,,,,〇,,,,
869,作業明細,2,プロジェクト,ユーザー,1,Administrator,管理者,〇,〇,〇,〇,〇,〇,〇,〇,〇,

注記

  • サイト取得APIのアクセス権(Permissions)は、「種別,ID,権限値」の文字列の配列で返ります。権限値は、読取り=1、作成=2、更新=4、削除=8、メール送信=16、エクスポート=32、インポート=64、サイトの管理=128、権限の管理=256の和です。
  • 権限を継承しているサイトのPermissionsは空で返ります。このサンプルでは、InheritPermissionのサイトのアクセス権を取得します。
  • 存在しないID・削除したサイト・レコードのIDは、取得に失敗し、備考にメッセージを入れて次のサイトへ進みます。
  • レコードごとのアクセス権と、グループのメンバーは出力しません。

対応バージョン

対応バージョン 内容
1.3.4.0 以降 機能追加