0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

プリザンターのサイト権限は、Sites.InheritPermission カラムを通じて親サイトの権限を継承する仕組みになっています。
この仕組みは「フォルダ配下の全サイトに同じ権限を一括適用したい」というケースには非常に便利ですが、継承ツリーの中の一部のサイトだけ特定のユーザを追加したい・除外したいといった「例外」は素直には扱えません。

やりたいこと 既定の挙動
親サイトの権限をそのまま使いつつ、特定の子サイトだけ「A さんを追加で見せたい」 子サイトの継承を切る必要があり、親の権限変更が反映されなくなる
親サイトの権限を継承しつつ、特定の子サイトだけ「B さんに見せたくない」 同上

本記事では、本体コードに手を入れずにこの「追加・除外」を実現する方法を、既存の拡張機能の組み合わせで考えてみます。
本体コード改修で正面から実装する方法は 後編:コード改修編 で扱います。

バージョン 1.5.3.0 を対象にしています

何が問題なのか(おさらい)

サイト権限は Permissions テーブルに 1 行ずつ保持され、Sites.InheritPermission がそのサイトの権限解決時に参照する ReferenceId を指します。

「子サイト β だけ D さんを追加したい」「子サイト α だけ A さんを外したい」と思っても、Permissions の行は ReferenceId=100 を共有している ため、片方だけに変更を加えることができません。
子サイトの継承を切って (InheritPermission = SiteId)、独自の Permissions 行を作れば例外を表現できますが、その瞬間に 親の権限変更が一切反映されなくなる という別の問題が発生します。

「継承は続けたい」「でも一部だけ例外を入れたい」を両立する仕組みが欲しい、というのが今回のテーマです。

全体の方針

既存の拡張機能だけで「継承を維持しつつ例外を入れる」のは、Sites.InheritPermission の動きそのものを書き換えない以上、難しいというのが正直なところです。
そこで本記事では発想を変え、「継承は切るが、親の権限変更を自動でフォローする」 ことで実質的に同じ結果を得るアプローチを採ります。

役割 使う仕組み
例外を定義する場所 対象サイトの「サイト概要」または親サイトの拡張プロパティ(JSON)
親の権限を取り込む バックグラウンドサーバスクリプトで Permissions テーブルを同期
例外を反映する 同スクリプトで「追加ユーザ」を INSERT、「除外ユーザ」を DELETE
例外サイトの確認 拡張 SQL で「親と権限が異なるサイト」を可視化

ポイントは次の 3 つです。

  1. 例外を入れたい子サイトの 継承を切るInheritPermission = SiteId)。
  2. 「親の権限 + 追加ユーザ − 除外ユーザ」を計算し、子サイトの Permissions 行に書き込む処理を バックグラウンドサーバスクリプトで定期実行する。
  3. 親サイトの権限が変わったタイミングでも、最大でも次回実行周期内には差分が解消される。

ステップ 1: 例外定義の場所を決める

例外情報(追加ユーザ・除外ユーザ)を保管する場所として、いくつかの選択肢があります。

保管場所 長所 短所
サイトの「サイト概要」 設定 UI ですぐ編集できる 自由記述のためフォーマット崩れに弱い
専用の管理テーブル(記録テーブル) 一覧・履歴・通知などが使える 1 サイト追加で済む
SiteSettings の拡張プロパティ サイトに紐づく設定として自然 直接編集には JSON 編集が必要

ここでは 専用の管理テーブル「権限例外」を用意するパターンを採用します。
カラム構成例は以下のとおりです。

項目 種類 用途
ClassA 文字列 対象サイトの SiteId
ClassB 文字列 親サイト(継承元)の SiteId
ClassC 文字列 動作モード(Add / Exclude
ClassD 文字列 対象ユーザの LoginId(カンマ区切り)
NumA 数値 付与する PermissionType(例: 1=閲覧、31=編集)

管理テーブルにすることで、例外設定そのものを 検索・履歴管理・通知 の対象にできます。

ステップ 2: 例外サイトの継承を切る

該当する子サイトを開き、「テーブルの管理 → アクセス権の管理」 から 「上位サイトから継承する」を解除 します。
内部的には Sites.InheritPermission = SiteId となり、Permissions テーブルにそのサイト専用の行が作成されます。

この段階では「親と同じ権限を独立して保持している状態」になります。以降は、この状態を 親の権限の変更に追従して同期 していくのがバックグラウンドサーバスクリプトの仕事です。

ステップ 3: バックグラウンドサーバスクリプトで同期する

「権限例外」テーブルに登録された行を読み、対象の子サイトの Permissions を「親サイトの権限+追加−除外」で再構成します。
プリザンターは拡張 SQL を Implem.Pleasanter/App_Data/Parameters/ExtendedSqls 配下に置くことで、サーバスクリプトから任意の SQL を実行できます。

拡張 SQL の準備

Implem.Pleasanter/App_Data/Parameters/ExtendedSqls/SyncPermissionOverride.sql
-- @ChildSiteId, @ParentSiteId, @AddLoginIds, @ExcludeLoginIds
-- 1) 子サイトの権限をいったん削除
DELETE FROM "Permissions" WHERE "ReferenceId" = @ChildSiteId;

-- 2) 親サイトの権限をコピー
INSERT INTO "Permissions"
    ("ReferenceId", "DeptId", "GroupId", "UserId",
     "DeptName", "GroupName", "Name", "PermissionType",
     "Creator", "Updator", "CreatedTime", "UpdatedTime")
SELECT
    @ChildSiteId, "DeptId", "GroupId", "UserId",
    "DeptName", "GroupName", "Name", "PermissionType",
    "Creator", "Updator", "CreatedTime", "UpdatedTime"
FROM "Permissions"
WHERE "ReferenceId" = @ParentSiteId
  -- 除外指定された LoginId のユーザを取り込まない
  AND "UserId" NOT IN (
      SELECT "UserId" FROM "Users"
      WHERE "LoginId" IN (SELECT value FROM STRING_SPLIT(@ExcludeLoginIds, ','))
  );

-- 3) 追加指定された LoginId のユーザを追加
INSERT INTO "Permissions"
    ("ReferenceId", "DeptId", "GroupId", "UserId",
     "DeptName", "GroupName", "Name", "PermissionType",
     "Creator", "Updator", "CreatedTime", "UpdatedTime")
SELECT
    @ChildSiteId, 0, 0, "UserId",
    '', '', "Name", @PermissionType,
    @CreatorId, @CreatorId, GETDATE(), GETDATE()
FROM "Users"
WHERE "LoginId" IN (SELECT value FROM STRING_SPLIT(@AddLoginIds, ','));

ここでは SQL Server を想定しています。PostgreSQL の場合は STRING_SPLITregexp_split_to_table などに置き換えてください。

バックグラウンドサーバスクリプト本体

「権限例外」テーブルに対するバックグラウンドサーバスクリプトとして、次のような実装を登録します。

// 権限例外サイトの SiteId
var overrideSiteId = 12345;

// 管理テーブルから例外定義を取得
var rows = items.Get(
    overrideSiteId,
    "ClassA, ClassB, ClassC, ClassD, NumA");

// ClassA (対象子サイト) と ClassB (親サイト) で集約する
var bySite = {};
rows.forEach(function (r) {
    var key = r.ClassA + ":" + r.ClassB + ":" + r.NumA;
    if (!bySite[key]) {
        bySite[key] = {
            ChildSiteId: parseInt(r.ClassA, 10),
            ParentSiteId: parseInt(r.ClassB, 10),
            PermissionType: parseInt(r.NumA, 10) || 31,
            Adds: [],
            Excludes: []
        };
    }
    var ids = (r.ClassD || "")
        .split(",")
        .map(function (s) { return s.trim(); })
        .filter(function (s) { return s !== ""; });
    if (r.ClassC === "Add") {
        bySite[key].Adds = bySite[key].Adds.concat(ids);
    } else if (r.ClassC === "Exclude") {
        bySite[key].Excludes = bySite[key].Excludes.concat(ids);
    }
});

// サイトごとに拡張 SQL を実行
Object.keys(bySite).forEach(function (key) {
    var s = bySite[key];
    extendedSql("SyncPermissionOverride", {
        ChildSiteId: s.ChildSiteId,
        ParentSiteId: s.ParentSiteId,
        AddLoginIds: s.Adds.join(","),
        ExcludeLoginIds: s.Excludes.join(","),
        PermissionType: s.PermissionType,
        CreatorId: context.UserId
    });
});

このスクリプトを バックグラウンドサーバスクリプトとして登録し、実行間隔(たとえば 10 分)を設定すれば、親サイトの権限が変更されても自動的に追従されます。
バックグラウンドサーバスクリプトの基本については「拡張サーバスクリプトとバックグラウンドサーバスクリプト」関連記事を参照してください。

即時反映を行いたいとき

定期実行では「親の権限を変えた瞬間には反映されない」という弱点が残ります。
親サイトの「アクセス権の管理」操作直後に即時反映したい場合は、次のいずれかで補えます。

方法 内容
親サイトの拡張サーバスクリプト(更新後) サイト保存後に、上記と同じロジックを子サイトに対して実行
「権限例外」テーブルの更新後スクリプト 管理テーブル側の更新時に、関連サイトの同期だけを実行
バックグラウンドスクリプトの実行間隔短縮 厳密性より運用の単純さを優先したい場合

ステップ 4: 例外サイトを可視化する拡張 SQL

例外が乱立すると「どのサイトが親と違う権限を持っているのか」が分からなくなりがちです。
親サイトの権限と子サイトの権限を比較する拡張 SQL を 1 本用意しておくと、棚卸しが容易になります。

Implem.Pleasanter/App_Data/Parameters/ExtendedSqls/ListPermissionOverride.sql
SELECT
    c."SiteId"          AS "ChildSiteId",
    c."Title"           AS "ChildTitle",
    p."SiteId"          AS "ParentSiteId",
    p."Title"           AS "ParentTitle",
    (SELECT COUNT(*) FROM "Permissions" WHERE "ReferenceId" = c."SiteId") AS "ChildPermCount",
    (SELECT COUNT(*) FROM "Permissions" WHERE "ReferenceId" = p."SiteId") AS "ParentPermCount"
FROM "Sites" c
INNER JOIN "Sites" p ON p."SiteId" = c."ParentId"
WHERE c."InheritPermission" = c."SiteId"   -- 継承を切っているサイト
  AND c."TenantId" = @_T
ORDER BY p."SiteId", c."SiteId";

「継承を切っているのに Permissions の件数が親と同じ」サイトは、ほぼ親と同期されていると見なせます。
件数差が大きい場合は、想定外の追加・除外がないかをチェックしましょう。

この方法の限界

便利な反面、純粋な「追加・除外」と完全に同じではありません。

限界 内容
即時性 バックグラウンド実行間隔のラグが発生する(即時反映には別途トリガーが必要)
整合性 親側の細かな変更(例:DeptId 経由のグループ追加)も同期対象になる
監査 Permissions の更新者がスクリプト実行者になり、本来の操作者と一致しない
例外の入れ子 親→子→孫と多段で継承を切る場合、同期順序を ParentSiteId 順で制御する必要がある

要件として「常に親と同じ+例外」が満たせれば十分であれば、本方式で十分実用に耐えます。
逆に「リアルタイムで例外を厳密に管理したい」「Permissions テーブルを直接更新したくない」のような要件がある場合は、後編で扱う コード改修によるネイティブ対応 を検討してください。

まとめ

項目 内容
課題 サイト権限の継承ツリーの一部だけにユーザを追加・除外できない
方針 子サイトの継承を切り、「親の権限+追加−除外」をバックグラウンドで同期する
構成要素 例外定義の管理テーブル + 拡張 SQL + バックグラウンドサーバスクリプト
メリット 本体コードに手を入れずに実現できる
デメリット 反映が非同期、Permissions を直接書き換えるため監査面の配慮が必要

「サーバスクリプトと拡張 SQL を組み合わせれば、ここまでできる」というイメージを掴んでもらえれば幸いです。
本体コードを改修して同じことを「ネイティブに」実現する方法は、続く 後編:コード改修編 で扱います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?