はじめに
プリザンターのサイト権限は、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 つです。
- 例外を入れたい子サイトの 継承を切る(
InheritPermission = SiteId)。 - 「親の権限 + 追加ユーザ − 除外ユーザ」を計算し、子サイトの
Permissions行に書き込む処理を バックグラウンドサーバスクリプトで定期実行する。 - 親サイトの権限が変わったタイミングでも、最大でも次回実行周期内には差分が解消される。
ステップ 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 の準備
-- @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_SPLIT を regexp_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 本用意しておくと、棚卸しが容易になります。
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 を組み合わせれば、ここまでできる」というイメージを掴んでもらえれば幸いです。
本体コードを改修して同じことを「ネイティブに」実現する方法は、続く 後編:コード改修編 で扱います。