Friday, May 29, 2020

ResourcePermissionの考察 (3)

前回、Liferayのリソース権限のDBレコードについて考察しました。今回は権限チェックの実行について考察したいと思います。

Liferayは開発者が直接にResourcePermissionを利用しないように設計されています。開発者フォーラムを見ているとit is inadvisable to manipulate the Liferay database directlydo not ever change it or write to the database(Liferayデータベースを直接操作することは推奨されていません。なので、直接変更したり、データベースに書き込んだりしないでください。)
のようなコメントがしばしばあります。そのため、Liferayはユーザ権限操作機能を提供しています。では、今回は権限操作機能中に一番基本のPermissionCheckerから考察しましょう。

PermissionCheckerのメソッド

Liferayでは、ユーザごとにPermissionCheckerのインスタンスを持っています。PermissionCheckerResourceLocalServiceを介してResourcePermissionテーブルレコードを確認する形でユーザの権限をチェックしてるため、権限チェックのメソッドhasPermission()メソッドシグネーチャResourcePermissionテーブルの要素になります。
boolean hasPermission(group, name, primKey, actionId);
  • LiferayのcompanyIdgroupから取得できるため、PermissionCheckerではcompanyIdに代わりにgroupをパラメータとして利用している
  • ユーザロールはPermissionCheckerのプロパティーのためメソッドに含まれていない
まとめると、以下の情報をPermissionCheckerに提供すれば、Liferayはユーザの権限を判断することができます。
  • リソースのグループID
  • リソース名
  • リソースのPrimeKey
  • アクションID
では、リソース毎にPermissionChecker提供する情報を考察しましょう。

Liferayにおいての権限チェック

あるユーザが、あるリソースに対する操作の権限をチェックする際の手順を考察しましょう。ResourcePermissionテーブルのレコードはロール毎に記録されているため、権限チェックの際、ユーザのロールを全部取得し、PermissionCheckerにおいて以下の判断を行うことが必要です。
※ 共通パラメータactionIdを除外し、roleIdPermissionCheckerが読み出せるため除外する
リソース種別 scope 必要パラメータ チェック内容
ポートレット 1 portletId
companyId
companyIdのLiferayインスタンス中
portletIdで特定されるポートレットに対する操作権限
primKey=companyId
2 portletId
groupId
groupIdのサイト中
portletIdで特定されるポートレットに対する操作権限
primKey=groupId
3 portletId サイトロールが割り当てられる際のみ
portletIdで特定されるポートレットに対する操作権限
primKey=0
4 portletId
layoutId
primKey=layoutId + _LAYOUT_ + portletId
で特定するポートレットに対する操作権限
インスタンス化
できない仮想
モデル
1 モデル名
companyId
companyIdのLiferayインスタンス中
モデル名で特定されるモデルに対する操作権限
primKey=companyId
2 モデル名
groupId
groupIdのサイト中
モデル名で特定されるモデルに対する操作権限
primKey=groupId
3 モデル名 サイトロールが割り当てられる際のみ
モデル名で特定されるモデルに対する操作権限
primKey=0
4 - 仮想モデルはインスタンスを持っていない
インスタンス化
可能なモデル
1 モデル名
companyId
当該ロールはポータル範囲の操作権限を持つこと
primKey=companyId
2 モデル名
groupId
groupIdのサイト中
モデル名で特定されるモデルに対する操作権限
primKey=groupId
3 モデル名 サイトロールが割り当てられる際のみ
モデル名で特定されるモデルに対する操作権限
primKey=0
4 モデル名
primaryKey
primaryKeyで特定されるモデルリソースに対する操作権限
Liferayでは、companyIdgrouplayoutなどエンティティーから取得できるため、権限チェックが必要なメソッドは以下となります。

リソース種別 scope 権限チェックメソッド Liferayのインターフェース
ポートレット 1 check(permissionChecker, group, portlet, action)
3の場合、groupId=0
PortletPermission
2
3
4 check(permissionChecker, group, layout, portletId, actionId)
インスタンス化
できない仮想
モデル
1 check(permisisonChecker, group, name, action) PortletResourcePermission
nameはプロパティとして実装クラスに登録する
2
3
4 - -
インスタンス化
可能なモデル
1 check(permissionChecker, primaryKey, actionId)
check(permissionChecker, model, actionId)
ModelResourcePermission
nameはプロパティとして実装クラスに登録する
groupIdはLiferaymodelにおいてgetGroupIdで取得する
2
3
4
ここまで考察すると、ようやく第一回の課題「Liferayが提供しているPermission関連クラスのシグネーチャが異なった理由」を解決しました。

ただし、まだ解決していない課題があります。現在のLiferayにおいて、モデルリソースとして定義される仮想モデルに対する権限チェックを行うクラス名はPortletResourcePermissionです。
  • 例として、ブログポートレットのcom.liferay.blogs仮想モデルに対するADD_ENTRY権限の判定は BlogsPermission.contains(permissionChecker, scopeGroupId, ActionKeys.ADD_ENTRY)が行います。BlogsEntryPermission.javaのソースコードを確認すると、PortletResourcePermissionをOSGIサービスとして参照してることが分かります。
正直、理由が分かりません。

権限チェックに追加されたクラス

※ 以下の内容はLiferay-CE 7.1に基づいています。7.0は一部ヘルパークラスを持っていません。

Liferay7.0の権限カスタマイズに詳しい方はもう気づいたかもしれませんか、Liferay7.1からの権限設定手順で、権限を登録するに、ModelResourcePermissionLogicPortletResourcePermissionLogicなどクラスは新規追加されます。では、Liferayの標準モジュールBlogsを考察し、新規されたクラスを整理しましょう。



ブログポートレットのUIにおいて
  • ブログポートレットの設定はリソースcom_liferay_blogs_web_portlet_BlogsPortletの権限CONFIGURATIONで決まります。
  • 新しいエントリーボタンの表示可否はモデルリソースcom.liferay.blogsの権限ADD_ENTRYで決まります。
  • permission-blogの編集可否はモデルリソースcom.liferay.blogs.model.BlogsEntryの権限UPDATEで決まります。
まず、インスタンス化可能なモデルリソースcom.liferay.blogs.model.BlogsEntryの権限チェックから考察しましょう。

インスタンス化可能なモデルリソースの権限チェック

ブログエンティティの更新ボタンの表示の判定はblogs-web/src/main/resources/META-INF/resources/blogs/entry_action.jspにおいてBlogsEntryPermission.contains(permissionChecker, entry, ActionKeys.UPDATE)が行なっています。このクラスから考察すると、Liferay7.1のモデルリソースは以下のような構造のことが分かります。



ブログポートレットは、DefaultModelResourcePermissionを介して、ModelResourcePermissionLogicPermissionCheckerを用いてあるブログエンティティーに対してユーザがもっている権限をチェックしています。では、DefaultModelResourcePermissionの流れを確認しましょう。


では、フローグラフ中のModelResourcePermisisonLogic[]はどこから登録されますか?その答えはBlogsEntryModelResourcePermissionDefinition.javaにあります。
@Override
public void registerModelResourcePermissionLogics(
 ModelResourcePermission modelResourcePermission,
 Consumer>
  modelResourcePermissionLogicConsumer) {

 modelResourcePermissionLogicConsumer.accept(
  new StagedModelPermissionLogic<>(
   _stagingPermission, BlogsPortletKeys.BLOGS,
   BlogsEntry::getEntryId));
 modelResourcePermissionLogicConsumer.accept(
  new WorkflowedModelPermissionLogic<>(
   _workflowPermission, modelResourcePermission,
   _groupLocalService, BlogsEntry::getEntryId));
}
続いてStagedModelPermissionLogic.javaWorkflowedModelPermissionLogic.javaを考察しましょう(StagingについてはStaging 公式ドキュメントを参考してください)。

  • StagedModelPermissionLogicは、StagingPermissionImpl.javaを介して、サイトページ状態がStageの場合、一定操作(例:ACCESS, VIEWなど)以外を禁止する
  • WorkflowedModelPermissionLogicは、WorkflowPermissionImpl.javaを介して、ユーザの公開前コンテントに対するアクセス権限を判定する

すなわち、登録したのは本来の権限判定対象であるブログエンティティのResourcePermissionテーブルレコードに関係ないロジックです。

ここまで考察すると、Liferay 7.1の権限構造をようやく理解できます。DefaultModelResourcePermissionModelResourcePermisisonLogicPermissionChecker両方を参照する理由は、ModelResourcePermisisonLogicPermissionCheckerの役割が違うのです:
  • 開発者はModelResourcePermissionLogicを介してModelResourcePermissionに登録したLiferayのResourcePermissionテーブルに関係ないビジネスロジック
  • 登録された権限ビジネスロジックは全てnullを返す場合、PermissionCheckerを利用し、ResourcePermissionテーブルからユーザ権限をチェックします。
Liferay 7.0のBlogsEntryPermission.javaと比べてみると、7.1の権限チェッククラスの各機能は切り離されていることが分かります。

Liferay 7.0 Liferay 7.1
BlogsEntryPermissionは直接的に権限判定を行う BlogsEntryPermissionModelResourcePermission
を介して権限判定を行う
StagingPermissionUtilWorkflowPermissionUtil
を直接参照する
OSGIサービスのxxxPermissionLogic方式で
ResourcePermissionテーブルに関係ない権限ロジック
を登録する
PermissionCheckerを直接参照する PermissionCheckerは直接参照しない

ポートレットリソースの権限チェック

ブログポートレットの設定などボタンの表示はcom_liferay_blogs_web_portlet_BlogsPortletポートレットリソースの権限チェックにおいて判定しています。ポートレット権限の判定はLiferayの組み込み機能のため、公式ドキュメントの説明の通りresource-actionsにアクション定義を入れたらLiferayは自動的にブログポートレット権限チェックを行います。
ポートレットの権限判定はモデルリソースのようなOSGI構造を持っていません。具体的にはPortletPermissionImpl.javaを参考してください。

仮想モデルリソースの権限チェック

  •  BlogsPermission.contains(permissionChecker, scopeGroupId, ActionKeys.ADD_ENTRY)が行います。
前述の通り、BlogsPermission.javaPortletResourcePermissionを参照し権限チェックを行なっています。PortletResourcePermissionの構造はModelResourcePermissionとほぼ同じのため、考察を省略します。



PermissionCheckerの正体

ここまで考察すると、Liferayでは、PortletPermissionPortletResourcePermissionModelResourcePermissionなどヘルパークラスを介してPermissionCheckerインタフェースを利用し、違うリソースに対する権限チェックを行っていることが分かりました。

では、最後にPermisisonCheckerの正体を確認しましょう。ポートレットJSPでPermissionCheckerのクラス名を出力すると、StagingPermissionChecker.javaのことが確認できますが、ソースコードを見ると、StagingPermissionCheckerはもう一つ_permissionCheckerをラップしています。続いてPermissionCheckerFactoryImpl.javaを確認すると、PermissionCheckerの正体はPropsValues、すなわちportal.propertiesで設定されたクラス名です。
public PermissionCheckerFactoryImpl() throws Exception {
  Class clazz =
    (Class)Class.forName(
      PropsValues.PERMISSIONS_CHECKER);

  _permissionChecker = clazz.newInstance();
}
皆さまは多分すでに存知ですが、LiferayのシステムPermissionCheckerは、portal-ext.propertiesで設定できます。
# Set the default permission checker class used by
# com.liferay.portal.security.permission.PermissionCheckerFactory to check
# permissions for actions on objects. This class can be overriden with a
# custom class that implements
# com.liferay.portal.security.permission.PermissionChecker.
#
#permissions.checker=com.liferay.portal.security.permission.SimplePermissionChecker
permissions.checker=com.liferay.portal.security.permission.AdvancedPermissionChecker
そして、Liferayのデフォルトの状態のPermissionCheckerの正体はAdvancedPermissionChecker.javaであることが分かりました。さらに、portal-ext.propertiesにカスタマイズクラスを登録するとLiferayのデフォルトPermissionCheckerを変更することもできます。

No comments: