特権ユーザの削除・編集の制限(改修案)
プリザンターでは、テナント管理者が特権ユーザを削除・編集できます。特権ユーザを保護する機能は本体にありません。このページは、特権ユーザの削除(と編集)を特権ユーザだけに限る本体改修の設計メモです。
本体を改修せずにできる範囲(OnSelectingWhere の拡張 SQL でユーザ一覧から隠す方法)とその限界は 拡張 SQL の活用 にあります。拡張 SQL では API からの /api/users/{id}/Delete を止められないため、ここでは権限判定そのものに手を入れます。
前提にした現行実装(1.5.8.1)
特権ユーザは
App_Data/Parameters/Security.jsonのPrivilegedUsersにLoginIdで列挙されたユーザで、Context.HasPrivilegeがtrueになり、ほとんどの権限チェックをバイパスします(アクセス権限の実装)。判定はPermissions.PrivilegedUsers(loginId)(Permissions.cs#L883-L887)。usersコントローラの削除可否は「CanManageTenantかつ本人でない」だけです(Permissions.cs#L608-L610)。csharpcase "users": return CanManageTenant(context: context) && context.UserId != context.Id;更新可否は「
CanManageTenantまたは本人 またはEnableManageTenant」です(Permissions.cs#L563-L566)。context.Idは URL の{id}(対象ユーザのUserId)です。画面の削除も API の削除もUserValidators.OnDeleting→context.CanDelete(ss)を通ります(UserValidators.cs#L1811-L1852)。ユーザ情報はテナントキャッシュから
SiteInfo.User(context, userId)で引けます(SiteInfo.cs#L439)。
図を読み込み中…
期待する動作
| 操作者 | 対象 | 削除 |
|---|---|---|
| テナント管理者 | 一般ユーザ | 可(現行どおり) |
| テナント管理者 | 特権ユーザ | 不可(新規) |
| 特権ユーザ | 別の特権ユーザ | 可 |
| 誰でも | 自分自身 | 不可(現行どおり。本人チェックが先に効く) |
画面と API の両方で同じ制限にします。
改修箇所の候補
| 案 | 改修箇所 | 長所 | 短所 |
|---|---|---|---|
| 1(推奨) | Permissions.CanDelete の case "users" | 権限判定が 1 か所に集まり、画面・API と、CanDelete で出し分けている削除ボタン(HtmlCommands.cs)に同時に効く | 判定のたびに SiteInfo.User を引く(キャッシュなので軽い) |
| 2 | UserValidators.OnDeleting の CanDelete の後 | ユーザ検証の中で完結し、専用のエラー種別を返しやすい | 削除ボタンの表示など CanDelete を使う他の箇所には効かない |
案 1 の改修イメージです。
case "users":
var targetUser = SiteInfo.User(
context: context,
userId: (int)context.Id);
return CanManageTenant(context: context)
&& context.UserId != context.Id
&& (!PrivilegedUsers(loginId: targetUser?.LoginId)
|| context.HasPrivilege);最後の条件の意味は次のとおりです。
- 対象が特権ユーザでない → 削除可能
- 対象が特権ユーザで、操作者も特権ユーザ → 削除可能
- 対象が特権ユーザで、操作者が特権ユーザでない → 削除不可
SiteInfo.User は該当ユーザがいないと null を返しうるため、targetUser?.LoginId のように null を考慮します(PrivilegedUsers(null) は false)。
案 2 の場合は、OnDeleting の CanDelete 判定の後に次を足します。
if (!context.HasPrivilege
&& Permissions.PrivilegedUsers(loginId: userModel.LoginId))
{
return new ErrorData(type: Error.Types.HasNotPermission);
}パラメータで切り替える案
既存環境の挙動を変えないよう、制限をパラメータで有効にする案です。削除だけでなく編集(CanUpdate)にも同じ制限をかけられるようにします。
方式 1: フラグ列挙型(推奨)
Implem.ParameterAccessor/Parts/Security.cs に列挙型とプロパティを追加します。
[Flags]
public enum PrivilegedUserRestrictions
{
None = 0,
Delete = 1,
Update = 2
}
public class Security
{
public List<string> PrivilegedUsers;
// 既存のプロパティ...
public PrivilegedUserRestrictions RestrictPrivilegedUsers;
}{
"PrivilegedUsers": ["admin"],
"RestrictPrivilegedUsers": "Delete, Update"
}| 設定値 | 意味 |
|---|---|
"None" | 制限なし(既定。現行どおり) |
"Delete" | 削除だけ制限 |
"Update" | 編集だけ制限 |
"Delete, Update" | 両方を制限 |
1 つのパラメータで複数の制限を持て、将来の追加もしやすい形です。
方式 2: 真偽値を 2 つ
public bool RestrictPrivilegedUserDeletion;
public bool RestrictPrivilegedUserUpdate;既存のパラメータの書き方に近く分かりやすい一方、制限の種類が増えるとパラメータも増えます。
判定の改修(方式 1 の場合)
削除:
case "users":
var canDelete = CanManageTenant(context: context)
&& context.UserId != context.Id;
if (canDelete
&& Parameters.Security.RestrictPrivilegedUsers.HasFlag(
PrivilegedUserRestrictions.Delete))
{
var targetUser = SiteInfo.User(
context: context,
userId: (int)context.Id);
if (PrivilegedUsers(loginId: targetUser?.LoginId)
&& !context.HasPrivilege)
{
return false;
}
}
return canDelete;編集:
case "users":
var canUpdate = CanManageTenant(context: context)
|| context.UserId == context.Id
|| context.UserSettings?.EnableManageTenant == true;
if (canUpdate
&& Parameters.Security.RestrictPrivilegedUsers.HasFlag(
PrivilegedUserRestrictions.Update))
{
var targetUser = SiteInfo.User(
context: context,
userId: (int)context.Id);
if (PrivilegedUsers(loginId: targetUser?.LoginId)
&& !context.HasPrivilege)
{
return false;
}
}
return canUpdate;対象が特権ユーザでなければ条件に当たらないため、一般ユーザが自分のプロファイルを編集する動作は変わりません。編集の制限を有効にすると、テナント管理者だけでなく EnableManageTenant(委任管理)のユーザも特権ユーザを編集できなくなります。削除は取り返しがつかないため有効を推奨し、編集は運用上の影響が大きいので必要なときだけ有効にするのが無難です。
専用のエラーメッセージ(任意)
HasNotPermission ではなく理由の分かるメッセージを返す場合は、エラー種別と表示文言を追加します。1.5.8.1 では、エラー種別の列挙は Implem.Pleasanter/Libraries/General/Error.cs、表示文言は App_Data/Displays/ の ID ごとの JSON(例: PermissionNotSelfChange.json)にあります。
| ID(案) | 日本語 | 英語 |
|---|---|---|
CannotDeletePrivilegedUser | 特権ユーザは削除できません。 | Cannot delete a privileged user. |
CannotUpdatePrivilegedUser | 特権ユーザは編集できません。 | Cannot update a privileged user. |
専用のエラーを返すには、CanDelete が bool を返す作りのため、案 2(OnDeleting で判定)と組み合わせます。
テスト観点
削除の制限が有効な場合:
- テナント管理者が一般ユーザを削除できる(現行どおり)
- テナント管理者が特権ユーザを削除しようとすると拒否される
- 特権ユーザが別の特権ユーザを削除できる
- 特権ユーザが自分を削除しようとすると拒否される(現行どおり)
- API(
/api/users/{id}/Delete)でも 1〜4 と同じ結果になる
編集の制限が有効な場合:
- テナント管理者が一般ユーザを編集できる
- テナント管理者・委任管理のユーザが特権ユーザを編集しようとすると拒否される
- 特権ユーザが別の特権ユーザを編集できる
- 一般ユーザ・特権ユーザが自分のプロファイルを編集できる
- API でも同じ結果になる
制限が無効(None)の場合は、テナント管理者が特権ユーザを削除・編集できる(現行どおり)ことを確かめます。
関連ページ
- 拡張 SQL の活用(拡張 SQL で一覧から隠す方法)
- api/users の実行権限
- アクセス権限の実装