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を変更することもできます。

Tuesday, May 26, 2020

ResourcePermissionの考察 (2)

前回、Liferayが一般ロールに与える権限を考察しました。今回は、サイトロール、組織ロールなどグループロールに対する権限の考察から始めようと思います。Liferay権限システムでは、サイトロールと組織ロールに付与される一般権限の適用範囲はグループテンプレートと認識し、ResourcePermissionテーブルではscope=3になります

それでは、前回と同じく、モデルリソースとポートレットリソース別でサイトロール、組織ロールに付与する権限を考察しましょう。

サイトロール x モデルリソース

以下の準備を行いましょう。設定方法は前回同様です。

  • blog_site_roleサイトロールを作成する
  • blog_site_roleを開き
    • ブログ/エントリー追加する権限を追加する
    • ブログのエントリ/表示権限を追加する
  • 前回、デフォルトサイトに作成したブログエンティティpermission-blogの権限定義において
    • blog_site_role更新権限を追加する
では、Role_ResourcePermissionテーブルを確認しましょう。

select roleId, name, type_ from Role_ where name = "blog_site_role";
select resourcePermissionId, name, scope, primKey, primKeyId, roleId, actionIds
from ResourcePermission where roleId = 50910;
結果は以下の通りです。

roleId ロール名 ロールtype
50910 blog_site_role 2
ID リソース名 scope primKey primKeyId roleId 操作
5226 com.liferay.blogs 3 0 0 50910 2
5227 com.liferay.blogs.model.BlogsEntry 3 0 0 50910 1
5228 com.liferay.blogs.model.BlogsEntry 4 50893 50893 50910 32
その結果が表す意味を考察しましょう。

ID ロール どんなリソース どの操作
5226 blog_site_role scope=3: サイトロールblog_site_roleを持つサイト中の全てcom.liferay.blogs ADD_ENTRY
5227 blog_site_role scope=3: サイトロールblog_site_roleを持つサイト中の全てcom.liferay.blogs.model.BlogsEntry VIEW
5228 blog_site_role scope=4: id=50893com.liferay.blogs.model.BlogsEntryインスタンス UPDATE
※ ユーザは複数サイト中同じサイトロールに割り当てられることができるため、ユーザがサイトロールを持つサイト中のみに、そのサイトロールに与えるscope=3の権限を持ちます。

サイトロール x ポートレットリソース

以下の準備を行いましょう。
  • blog_site_roleを開いて、権限定義に
    • アプリケーション権限/ページに追加する権限を追加する
  • デフォルトサイトのブログポートレットに
    • ポートレット権限設定を開いてblog_site_role設定権限を追加する
それ後、ロールとResourcePermissionテーブルで確認しましょう。結果は以下の通りです(重複するデータについては除外します)。


ID リソース名 scope primKey primKeyId roleId 操作
5232 com_liferay_blogs_web_portlet_BlogsPortlet 3 0 0 50910 2
5233 com_liferay_blogs_web_portlet_BlogsPortlet 4 38656_LAYOUT_com_liferay_blogs_web_portlet_BlogsPortlet 0 50910 4
結果を考察しましょう。

ID ロール どんなリソース どの操作
5232 blog_site_role scope=3: サイトロールblog_site_roleを持つサイト中の全てBlogsPortlet ADD_ENTRY
5228 blog_site_role scope=4: id=38656のレイアウト上のBlogsPortlet UPDATE

組織ロール

Liferayでは組織ロールの挙動はサイトロールとほぼ一致のため省略します。

まとめ

ここまで考察した結果をまとめましょう。scope=3のサイトロールと組織ロールの適用範囲はユーザが当該ロールを割り当てられているかとうかで決まります。

  • 特定インスタンスを持っていないリソース
    • サイトロールの権限の適用範囲は、ユーザが当該ロールを割り当てられているサイトのみです。そのため、リソースprimKeyは0になります。
    • 組織ロールの権限の適用範囲は、ユーザが当該ロールを割り当てらている組織の組織サイトです。そのため、リソースprimKeyは0になります。
  • リソースの特定インスタンス
    • 権限の適用範囲はnameprimKeyが特定できるリソースのみです。

リソース種類 特定インスタンス リソース名 scope primKey 適用範囲
モデル N モデル名 1 companyId primKeyが表すLiferayインスタンス
モデル N モデル名 3 0 roleIdが表すロールを持ち場所(サイトまたは組織のサイト)中の全モデルインスタンス
モデル Y モデル名 4 リソースId primKeyが表すモデルインスタンス
ポートレット N ポートレットキー 1 companyId primKeyが表すLiferayインスタンス
ポートレット N ポートレットキー 3 0 roleIdが表すロールを持ち場所(サイトまたは組織のサイト)中の全primKeyが表すポートレット
ポートレット Y ポートレットキー 4 layout
ポートレットキー
primKeyが表すポートレット

その他

チームロール
Liferayでは、サイト内のチームが作成できます。チームに権限を与えることもできます。例として、デフォルトサイトにteam-1チームを作成した後に、前回作成したpermission-blogの権限設定を開いてteam-1ロールを確認できます。

では、team-1ロールに更新する権限を追加し、データベースを確認しましょう。

select teamId, name from Team where name = "team-1";
select roleId, name, type_ from Role_ where name = "50924";
select resourcePermissionId, name, scope, primKey, primKeyId, roleId, actionIds
from ResourcePermission where roleId = 50925;
結果は以下の通りです。

teamId チーム名
50924 team-1
roleId ロール名 ロールtype
50925 50924 4
ID リソース名 scope primKey primKeyId roleId 操作
5239 com.liferay.blogs.model.BlogsEntry 4 50893 50893 50925 32
チームを作成後、Liferayは自動的にclassPk=teamIdclassName=チームのロールを作成します。そのロールに与える権限はチームの所属サイト上のブログインスタンスとブログポートレットインスタンスだけのため、チームロールの権限レコードはscope=4になります。
scope=2
Liferay公式ブログ記事の説明の通り、scope=2(グループ範囲)のResourcePermissionレコードが存在します。ただし、今まで検証した権限のscopeはいずれでも2になりません。では、scope=2の権限は一体何でしょうか?

答えは、指定されたグループ(=サイト)内のリソースのみに有効する権限です。以下の手順で検証しましょう。

  • 一般ロールblog_roleを開き
    • 権限の定義ブログ/権限設定を追加する
    • 追加の際、権限設定項目の右の変更ボタンを押し、Liferay DXP(デフォルトサイト)とユーザー非公開サイトを選択する
その後、データベースをチェックしましょう。

select resourcePermissionId, name, scope, primKey, primKeyId, roleId, actionIds
from ResourcePermission where roleId = 50867 and scope = 2;
結果はご覧の通り、scope=2primKeyLiferay DXPサイトとユーザー非公開サイトgroupIdの権限レコードが作成されました。

ID リソース名 scope primKey primKeyId roleId 操作
5324 com_liferay_blogs_web_portlet_BlogsPortlet 2 20126 20126 50867 8
5325 com_liferay_blogs_web_portlet_BlogsPortlet 2 20132 20132 50867 8
では、scope=2の権限とscope=3のサイトロールの権限の違いを考察しましょう。

scope 適用範囲
2 primKeyが表すサイト内のリソース、1個レコードの適用サイト数は1
3 roleIdが表すサイトロールを持つサイト内のリソース 、1個レコードは複数サイト適用可能

次回はここまて考察した内容をLiferayカスタマイズでの運用を考察したい思います。

Wednesday, May 20, 2020

ResourcePermissionの考察 (1)

こんにちは。ウです。

みな様がLiferayカスタマイズをする時、権限周りのメソッド"シグネチャーに困ったことがありますか?例えば、PermissionCheckerとか、Liferay 7.1から新規したヘルパークラスModelResourcePermission.javaPortletResourcePermission.javaなどにおいてはメソッド中、このような定義がよく見えます。

//PermissionChecker
public boolean hasOwnerPermission(long companyId, String name, long primKey, long ownerId, String actionId);

public boolean hasPermission(Group group, String name, long primKey, String actionId);

// ModelResourcePermission
public void check(PermissionChecker permissionChecker, long primaryKey, String actionId);

public void check(PermissionChecker permissionChecker, T model, String actionId);

// PortletResourcePermission
public void check(PermissionChecker permissionChecker, Group group, String actionId);

どの場面でどのメソッドを利用しますか?利用の際どの値をメソッドに与えますか?そのメソッドシグネチャーはLiferayの権限管理の関係はどうですか?今回はこの権限周りにいて考察しようと思います。

ちなみに、みなさまはLiferayオフィシャルブログの権限関連記事を読んだことがありますか?この記事はLiferayのロールと権限の基本関係を説明しているのでぜひおすすめです。

Liferayの権限

みな様のご存知通り、Liferayの権限システムには、ポートレットリソースまたはモデルリソース毎の粒度で行われています。従って:

  • Liferayリソース(BlogJournalArticleまたはDLFileEntryなど)の初期パーミッションは全部ResourceLocalService.addResourcesに介してResourcePermissionテーブルにレコードを作成している(例:JournalArticleLocalServiceImpl.java)。
  • 逆に、PermissionCheckerがユーザの権限をチェックする際、同じくResourceLocalService.hasUserPermissionなどのメソッドを利用している(例:AdvancedPermissionChecker.java)。

すなわち、Liferayでは、ResourcePermissionPermissionCheckerを利用し本当のResourcePermissionテーブルの詳細情報を隠しています。Liferayの権限システムを深く理解したい場合、ResourcePermissionテーブルとリソース権限の関係を考察しないといけない理由はそこにあります。

ResourcePermissionテーブル

Liferayが行なっている権限管理はロールベースアクセス制御(RBAC)方式のため、最終的にデータベースに記入するレコードは以下の内容になります:
  • あるロールが
  • あるリソースに対して
  • どんな操作が許可される
ResourcePermissionテーブルはその三つの情報を含んでいます。上記Liferayオフィシャルブログ記事を参考して具体的に整理してみましょう。

  • name: リソース名
    • ポートレットリソース:ポートレットキー
    • モデルリソース:モデル名
  • primKey: リソースID(権限範囲)
    • 全Liferayインスタンス有効権限: companyId
    • グループ内有効: groupId
    • 具体的なリソースに対する権限:
      • モデルリソース: リソースprimKey
      • ポートレットリソース: layoutId + _LAYOUT_ + portletId
  • scope: 権限の範囲
    • 1: 全Liferayインスタンスに有効
    • 2: グループ内のみ有効
    • 3: グループテンプレート(サイト、組織)内のみ有効
    • 4: 具体的なリソースインスタンスに対する権限
  •  roleId: ロールID
  •  actionId: 権限が許可する操作

まとめると、以下のグラフのようにRBACに必要な情報をレコードで格納しています。


それでは、具体的にどのような内容が格納されるのかを確認しましょう。

考察

今回はLiferayの標準モジュールBlogポートレットを利用してResourcePermissionテーブル中一般ロールに対する権限の表現を確認しましょう。

一般ロール x モデルリソース

以下の準備を行いましょう:

  • 一般ロールblog_roleを作成する
  • blog_roleを開いて、権限定義に
    • ブログ/エントリー追加する権限を追加する
    • ブログのエントリ/表示権限を追加する


  • デフォルトサイトにブログポートレットを置き、その中にpermisison-blogというブログエンティティを作成する
  • permission-blogの権限定義に、
    •  blog_role表示更新を追加する

その後、ロールとResourcePermissionテーブルで確認しましょう。
select roleId, name, type_ from Role_ where name = "blog_role";
select resourcePermissionId, name, scope, primKey, primKeyId, roleId, actionIds from ResourcePermission where roleId = 50867;

結果は以下の通りです。
roleId ロール名 ロールtype
50867 blog_role 1
ID リソース名 scope primKey primKeyId roleId 操作
5207 com.liferay.blogs 1 20099 20099 50867 2
5218 com.liferay.blogs.model.BlogsEntry 1 20099 20099 50867 1
5219 com.liferay.blogs.model.BlogsEntry 4 50893 50893 50867 33

では、その結果はどのような権限を表すのかについて考察しましょう。
ID ロール どんなリソース どの操作※
5207 blog_role scope=1: liferayインスタンス20099中の全てcom.liferay.blogs ADD_ENTRY
5218 blog_role scope=1: liferayインスタンス20099中の全てcom.liferay.blogs.model.BlogsEntry VIEW
5219 blog_role scope=4: id=50893com.liferay.blogs.model.BlogsEntryインスタンス VIEW + UPDATE

一般ロール x ポートレットリソース

続いて、以下の準備を行いましょう。
  •  blog_roleを開いて、権限定義に
    • アプリケーション権限/ページに追加する権限を追加する

  • デフォルトサイトのブログポートレットに
    • ポートレット権限設定を開いてblog_role設定権限を追加する

それ後、ロールとResourcePermissionテーブルで確認しましょう。
select resourcePermissionId, name, scope, primKey, primKeyId, roleId, actionIds from ResourcePermission where roleId = 50867;

結果は以下の通りです(重複レコードを除外します)。
ID リソース名 scope primKey primKeyId roleId 操作
5222 com_liferay_blogs_web_portlet_BlogsPortlet 1 20099 20099 50867 2
5223 com_liferay_blogs_web_portlet_BlogsPortlet 4 38656_LAYOUT_com_liferay_blogs_web_portlet_BlogsPortlet 0 50867 4

では、その結果はどのような権限を表すのかについて考察しましょう。
ID ロール どんなリソース どの操作
5222 blog_role scope=1: liferayインスタンス20099中の全てBlogsPortlet ADD_TO_PAGE
5219 blog_role scope=4: id=38656のレイアウト上のBlogsPortlet CONFIGURATION

まとめ

ここまでチェックすると、権限を与えられるリソースは三種類があることがわかりました。
  • 具体的なインスタンスを持っていないリソース
    • そのモデルクラスは存在しないためインスタンス化できないため仮想モデルとして認識します。
      • 例:リソースcom.liferay.blogsはLiferayブログその概念を表すものと認識できます。
    • そのリソースに与えられる権限は、具体的なブログエンティティに関係ありません。
      • 権限例:新規作成、パーミッション設定、ブログの購読
    • 特定できるインスタンスに適用しないため、scopeは4になれません。
    • primKeyは権限が適用される範囲の対象を表します。
      • scope=1の場合、primKeyはliferayインスタンスIDです。
  • 任意ある種類のインスタンス化可能なリソース
    • 対象は任意Liferayモデルまたはポートレット
      • 例:リソースcom.liferay.blogs.model.BlogsEntry, scope=1は全Liferayインスタンス中の任意Liferayブログを表し、そのリソースに与える権限は全Liferayインスタンス中のブログで有効となります。
      • 例:リソースcom_liferay_blogs_web_portlet_BlogsPortlet, scope=1は全Liferayインスタンス中任意ブログポートレットを表し、そのリソースに与える権限は全Liferayインスタンス中のブログポートレットで有効となります。
    • 操作は具体的にインスタンスに対するアクションです。
      • 例:参照、更新、削除、コメント追加、ポートレット追加
    • 任意インスタンスに適応する権限のため、scopeは4になれません。
    • primKeyは権限が適用される範囲の対象を表します。
      • scope=1の場合、primKey=LiferayインスタンスID
  • 特定したリソース
    • 対象は任意Liferayモデルまたはポートレット
      • 例:リソースcom.liferay.blogs.model.BlogsEntry, scope=4primKeyに指定するブログエンティティと認識します。その権限はその特定のブログエンティティのみで有効です。
      • 例:リソースcom_liferay_blogs_web_portlet_BlogsPortlet, scope=4primKeyに指定するブログポートレットと認識します。その権限はその特定のブログポートレットので有効です。
    • 操作は具体的にインスタンスに対するアクションです。
    • primKeyは特定インスタンスのIDになります。
      • モデルリソースの場合、primKey=モデルID
      • ポートレットリソースの場合、primKey=layoutId + _LAYOUT_ + ポートレットキー
次回、サイトロールなどグループロールに与える権限を考察しようと思います。


※ actionIdの内容は以下のsqlで取得できます。
select name, actionId, bitwiseValue from ResourceAction where name="com.liferay.blogs" and bitwiseValue = 2;
select name, actionId, bitwiseValue from ResourceAction where name="com.liferay.blogs.model.BlogsEntry" and (bitwiseValue = 1 or bitwiseValue=32);
select name, actionId, bitwiseValue from ResourceAction where name="com_liferay_blogs_web_portlet_BlogsPortlet " and (bitwiseValue = 2 or bitwiseValue = 4);
リソース名操作bitwiseValue
com.liferay.blogsADD_ENTRY2
com.liferay.blogs.model.BlogsEntryVIEW1
com.liferay.blogs.model.BlogsEntryUPDATE32
com_liferay_blogs_web_portlet_BlogsPortletADD_TO_PAGE2
com_liferay_blogs_web_portlet_BlogsPortletCONFIGURATION4

Friday, May 1, 2020

Tomcat9をアップデートしたらWebサーバ(Apache)とAJP接続できなくなった話

こんにちは。おおたにです。

先日Tomcat9を最新バージョン(その時点では9.0.33)にアップデートしたところ、WebサーバとのAJP接続がうまくいかなくなったので、今回はその対処法を紹介します。

原因


こちらの脆弱性への対応のため、Tomcat 9.0.31でAJP1.3コネクタ設定が変更されたことが原因でした。具体的な変更点はTomcat 9.0.31のchangelogにありますが、主なものは以下の3点です。
  • AJPコネクタ(8009番ポートのやつ)がデフォルトでdisabledになった
  • バインドアドレスのデフォルトがIPv6ループバックアドレス(::1)になった
  • 新しい属性secretRequiredが追加され、デフォルトでtrueとなっている(trueの場合、secret属性でシークレットキーを指定する必要がある)

以下、上記変更に対応するための設定方法です。今回はApache HTTP Serverを例に説明しますが、Apache HTTP Serverのバージョンにより設定が若干異なります。

Apache HTTP Server 2.4.43以降の場合


Tomcat側は、<TOMCAT_DIR>/conf/server.xmlでAJPコネクタのコメントアウトを外した後、以下のように設定します。
    <Connector protocol="AJP/1.3"
               address="TomcatサーバのIPアドレス"
               port="8009"
               secret="シークレットキー"
               redirectPort="8443" URIEncoding="UTF-8" />
  • address : TomcatサーバのIPアドレス。複数のインタフェースが存在する場合はWebサーバとの通信に利用する方のIPアドレスを指定する。
  • secret : AJPコネクタのシークレットキー。

Apache HTTP Server側は、ProxyPass設定にsecretパラメータを追加して先ほどと同じ値を指定します。
ProxyPass / ajp://TomcatサーバのIPアドレス/ secret=シークレットキー

Apache HTTP Server 2.4.41以前の場合


AJPコネクタのシークレットキーに対応できないため、Tomcat側でsecretRequiredfalseに設定する必要があります。
    <Connector protocol="AJP/1.3"
               address="TomcatサーバのIPアドレス"
               port="8009"
               secretRequired="false"
               redirectPort="8443" URIEncoding="UTF-8" />
Apache HTTP Server側は特に設定変更しなくてOKです。


弊社ではLiferayやAlfrescoといったオープンソース製品のカスタマイズ/導入支援を行っておりますが、Tomcat/Apacheを使ってホストすることが多いためさっそくこの問題にあたりました。どうぞご参考まで。