Tuesday, July 7, 2020

Liferay 7をインストールしてみよう

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

今回はLiferay 7のインストール方法について紹介します。

本ブログでもたびたび取り上げているLiferayは、オープンソースのデジタルエクスペリエンスプラットフォーム(DXP)製品です。従来はエンタープライズ向け情報ポータル(Enterprise Information Portal)と呼ばれていたジャンルですが、よりユーザ体験や顧客体験にフォーカスするために現在はDXPと呼ばれています。

企業のインターネット向けサイトやイントラネット向けサイト、代理店/販売店ポータルなどに代表される組織/企業間、コミュニティポータルなどに代表されるユーザ同士など、様々な形での情報発信/情報共有の場を構築することに利用されています。さらにはバックエンドにあるシステムの情報や他のサービスを通して得られる情報を統合し、B2B/B2C、パブリック/プライベート問わず様々なWebサイト上で最適なユーザ体験を提供することができるプラットフォームです。

エンタープライズ向けと銘打たれているのでインストールの敷居も高いのではないかと思われるかもしれませんが、そんなことはありません。もちろん実運用を考えると諸々の設定やチューニングを行う必要がありますが、とりあえずインストールして使ってみようという限りではとても簡単にセットアップすることができます。

CE(Community Edition)とDXP(Digital Experience Platform)


LiferayにはCE(Community Edition)とDXP(Digital Experience Platform)の2つのエディションがあります。

CE : 無償利用できるけどLiferay社のサポートが受けられないバージョンです。記事執筆時点ではLiferay Portal 7.3.2 CE GA3が最新のバージョンです。

DXP : 有償のサブスクリプションが必要だけどLiferay社のサポートを受けられるバージョンです。セキュリティ関連を含む修正パッチの提供が受けられる他、クラスタリングやAudit等の構成/運用面で助けとなる機能が提供されます。記事執筆時点ではLiferay DXP 7.2 SP2が最新のバージョンです。

なお、DXPをご検討中の方やご質問/ご相談のある方はこちらのフォームから弊社までご連絡ください。

Javaのインストール


Liferayの動作にはJavaが必要です。CEはこちら、DXPはCompatibility Matrixで必要なJavaのバージョンが確認できます。
Liferay Portal 7.3 CEやLiferay DXP 7.2であればOracle JDK 8が必要です。こちらからダウンロードしてインストールしてください。

Liferayのダウンロード


CEはLiferay Community Downloadsの「Liferay Portal」から、DXPはLiferay Help Centerからダウンロードできます。Bundled with Tomcat (tar.gz)もしくはBundled with Tomcat (7-Zip)を選択してダウンロードします。
Apache Tomcatが同梱されて必要最低限の設定も行われているため、さくっと動かすにはこのパッケージがお勧めです。

Liferayのインストール


次に、先ほどダウンロードしたLiferayのファイルを展開します。liferay-ce-portal-7.xxxxもしくはliferay-dxp-7.xxxxというフォルダが作成され、その中に必要なファイルがコピーされます。なお、このフォルダは<LIFERAY_HOME>と呼ばれ、Liferayの設定や運用に際して重要なフォルダとなります。

以上でLiferayのインストールは終わりです。簡単!

なお、実運用を考えると別途データベースを用意したりJVMをチューニングしたりなどの設定が必要ですが、ひとまず動けばOKということであればデフォルトのままで十分です。

[DXPのみ] ライセンスファイルのコピー


DXPの場合はLiferay社から提供されるライセンスファイルを<LIFERAY_HOME>/deployフォルダにコピーします。Liferay起動時にライセンスファイルが読み込まれ、認証をパスして初めてLiferayが使えるようになります。

Liferayの起動と初期設定


では、早速Liferayを起動してみましょう。Liferayを起動するためには、以下のTomcat起動スクリプトを実行します。

Windows : <LIFERAY_HOME>/tomcat-xxxx/bin/startup.bat
Linux (or Mac OS X) : <LIFERAY_HOME>/tomcat-xxxx/bin/startup.sh

Liferayが正常に起動すると、ブラウザが自動的に起動してhttp://localhost:8080にアクセスします。起動しない場合はブラウザを起動して先のURLにアクセスしてみてください。以下のような画面が表示されればOKです。


これは初回起動時のみ表示される設定画面で、ポータルの名称やデフォルト言語、管理者ユーザの情報、データベース接続を設定することができます。デフォルトでは組込みDBのHypersonicが使われますが、お試し用途であればこのままでOKです。
Email欄に管理者のメールアドレスを入力して「Finish Configuration」をクリックします。しばらく待つと設定が完了して利用規約が表示されるので、「I Agree」をクリックします。


続いて管理者ユーザのパスワードを入力して「Save」をクリックします。


最後にパスワードリマインダ(パスワードを忘れた時の秘密の質問)を入力して「Save」をクリックします。


設定が完了すると、以下のランディングページが表示されます(上がLiferay DXP 7.2、下がLiferay Portal 7.3 CE)。



以上でLiferayが使える状態になったかと思います。みなさまも是非Liferayをインストールして実際に触ってみてください!

トラブルシューティング


Liferayが正常に起動しない場合は、以下のログの内容を確認してみてください。

<LIFERAY_HOME>/logs/liferay.xxxx-xx-xx.log
<LIFERAY_HOME>/tomcat-xxxx/logs/catalina.xxxx-xx-xx.log

また、LiferayをインストールしたPCのメモリも確認してください。Liferayはデフォルトでヒープに2.5GB、MetaSpaceに768MB使う設定となっています。PCのメモリは4GBだと厳しいかもしれません。少なくとも6GBは欲しいところです。

Monday, June 29, 2020

Liferayテーブルを見てみよう〜ユーザ情報編

こんにちはナクラです。

暑くなったり、少しすごしやすくなったりと気候がころころ変わるこの頃ですが、
徐々に真夏に近づいているのを実感する毎日ですね。

さて、今日はLiferayのデータベースのテーブルについて書きたいと思います。
Liferayでは253ものデータベーステーブルが用意されています。
なので、全体にたいしてER図を描くのはあまり現実的ではないのです。

また、ユーザインタフェース上の設定項目が実際にはどのテーブルに登録されているのかをまとまって見られる資料もなかなか良いものがないので、
ここで、個別に確認していってみようと思います。

今回はユーザの設定画面について確認していくことにしましょう。
ユーザの情報はコントロールパネルの
[ユーザ] > [ユーザと組織] の項目から確認できます。
個別のユーザの情報を見ると
次の図のようになっています。



ユーザ情報の画面では、このように
・共通タブ
・連絡先タブ
・表示設定タブ
の3つのタブがありますので、一つづつ見ていきましょう。


共通タブ

まず共通タブから見ていきます。
共通タブの中にもいくつかの項目があります。
  • 情報
  • 組織
  • メンバーシップ
  • ロール
  • プロフィールとダッシュボード
  • パスワード
  • アプリ

それぞれを見ていきましょう。

【情報】
この画面の設定項目の大半は
ユーザ情報のメインのデータであるUser_テーブルに含まれている情報です。
User_テーブルは最後に"_"がついているのに注意してくだあい。
よくつけ忘れてテーブルがない!と慌ててしまうことがあります。)

ただし、
性別、生年月日、サフィックスについては
Contact_テーブルに含まれる情報になっています。(これも"_"がついています)
また、
詳細情報のセクションについては各設定項目でテーブルが違っています。
カテゴリについては
AssetEntryAssetCategoryRelテーブル
+--------------------------------------+-------------+------+-------+-----------+-------+
| Field                                   | Type        | Null | Key  | Default | Extra |
+--------------------------------------+-------------+------+-------+-----------+-------+
| mvccVersion                          | bigint(20) | NO  |         | 0          |         |
| assetEntryAssetCategoryRelId | bigint(20) | NO  | PRI   | NULL    |         |
| assetEntryId                          | bigint(20) | YES  | MUL | NULL    |         |
| assetCategoryId                     | bigint(20) | YES  | MUL | NULL    |         |
| priority                                 | int(11)      | YES  |        | NULL    |         |
+-------------------------------------+--------------+------+--------+----------+-------+

タブについては
AssetEntries_AssetTagsテーブル
+---------------+------------+-------+-------+-----------+-------+
| Field          | Type       | Null | Key  | Default | Extra |
+---------------+------------+-------+-------+-----------+-------+
| companyId | bigint(20) | NO  | MUL | NULL    |          |
| entryId      | bigint(20) | NO  | PRI   | NULL    |          |
| tagId         | bigint(20) | NO  | PRI   | NULL    |          |
+---------------+------------+-------+-------+-----------+-------+
に情報が登録されます。
ユーザのuserIdの値が
AssetEntryAssetCategoryRelではassetEntryIdのカラムに
AssetEntries_AssetTagsではentryIdのカラムに
設定されます。

カルタムフィールドについてはExpandoという機構を利用していますが、
これについては別の機会にお話したいと思います。

【組織】
ユーザが所属する組織の情報については
Users_Orgsテーブル
+-------------------+-------------+-------+------+-----------+-------+
| Field               | Type        | Null | Key | Default | Extra |
+-------------------+-------------+-------+------+-----------+-------+
| companyId      | bigint(20) | NO   | MUL | NULL    |        |
| organizationId | bigint(20) | NO   | PRI  | NULL    |        |
| userId             | bigint(20) | NO   | PRI  | NULL    |        |
+--------------------+------------+------+--------+----------+-------+
に設定されています。

【メンバーシップ】
ユーザが所属するサイトの情報については
Users_Groupsテーブル
+---------------+--------------+------+-------+-----------+-------+
| Field          | Type         | Null | Key | Default  | Extra |
+---------------+--------------+------+-------+-----------+-------+
| companyId | bigint(20) | NO   | MUL | NULL     |         |
| groupId      | bigint(20) | NO   | PRI  | NULL     |         |
| userId        | bigint(20) | NO   | PRI  | NULL     |         |
+---------------+--------------+------+-------+-----------+-------+
に設定されています。

ユーザが所属するユーザグループの情報については
Users_UserGroupsテーブル
+-----------------+-------------+-------+-----+-----------+--------+
| Field            | Type        | Null | Key | Default | Extra |
+-----------------+-------------+-------+------+----------+--------+
| companyId   | bigint(20) | NO   | MUL | NULL   |         |
| userId          | bigint(20) | NO   | PRI  | NULL    |         |
| userGroupId | bigint(20) | NO   | PRI  | NULL    |         |
+-----------------+-------------+-------+------+----------+--------+
に設定されています。

【ロール】
ユーザが割り当てられているロールの情報については
Users_Rolesテーブル
+---------------+-------------+-------+-------+-----------+--------+
| Field          | Type        | Null | Key   | Default | Extra |
+---------------+-------------+-------+-------+-----------+--------+
| companyId | bigint(20) | NO   | MUL | NULL     |          |
| roleId        | bigint(20) | NO   | PRI   | NULL     |          |
| userId        | bigint(20) | NO   | PRI   | NULL     |         |
+---------------+-------------+-------+-------+-----------+--------+
に設定されています。


【プロフィールとダッシュボード】
この項目からはユーザ個人の公開サイト(プロフィール)、非公開サイト(ダッシュボード)へ移動できます。
ユーザの個人サイトの情報は
Group_テーブル
+----------------------------------+-----------------+------+---------+-----------+-------+
| Field                                  | Type            | Null | Key    | Default | Extra |
+----------------------------------+-----------------+------+---------+-----------+-------+
| mvccVersion                      | bigint(20)     | NO   |         | 0            |         |
| uuid_                                | varchar(75)   | YES  | MUL  | NULL      |         |
| groupId                             | bigint(20)      | NO   | PRI   | NULL      |         |
| companyId                        | bigint(20)      | YES  | MUL  | NULL      |         |
| creatorUserId                    | bigint(20)      | YES  |         | NULL      |         |
| classNameId                      | bigint(20)      | YES  | MUL  | NULL      |         |
| classPK                              | bigint(20)      | YES  |        | NULL       |         |
| parentGroupId                   | bigint(20)      | YES  |         | NULL      |         |
| liveGroupId                        | bigint(20)     | YES  | MUL  | NULL      |         |
| treePath                            | longtext        | YES  |        | NULL       |         |
| groupKey                           | varchar(150)  | YES  |        | NULL       |        |
| name                                 | longtext        | YES  |        | NULL       |        |
| description                        | longtext        | YES  |         | NULL       |        |
| type_                                | int(11)           | YES  | MUL | NULL       |        |
| typeSettings                      | longtext         | YES  |        | NULL       |        |
| manualMembership            | tinyint(4)       | YES  |        | NULL       |        |
| membershipRestriction      | int(11)           | YES  |        | NULL       |         |
| friendlyURL                       | varchar(255)  | YES  |        | NULL       |         |
| site                                   | tinyint(4)       | YES  |        | NULL       |         |
| remoteStagingGroupCount | int(11)           | YES  |        | NULL       |         |
| inheritContent                   | tinyint(4)       | YES  |        | NULL       |        |
| active_                              | tinyint(4)       | YES  |        | NULL       |        |
+----------------------------------+------------------+-------+-------+------------+-------+
に設定されています。
このテーブルのClassPKカラムの値にユーザのuserIdと同じ値が設定されているものがユーザ個人のサイトになります。

【パスワード】
パスワードについては
User_テーブルのpassword_カラムに値が登録されています。

【アプリ】
デフォルトでは設定されていません。

連絡先タブ

次に連絡先タブについてみていきましょう。
連絡先タブには
  • 住所
  • 連絡先情報

の2つの項目があります。



【住所】
ユーザの住所情報については
Addressテーブル
に登録されます。

【連絡先情報】
ユーザの連絡先情報については、いろいろな項目があります。
各々の設定については以下のようになっています。
  • 電話番号       :Phoneテーブル
  • 追加メールアドレス  :EmailAddressテーブル
  • Webサイト       :Websiteテーブル
  • Jabber       :Contact_テーブルのjabberSnカラム
  • Skype        :Contact_テーブルのskypeSnカラム
  • SMS        :Contact_テーブルのsmsSnカラム
  • Facebook       :Contact_テーブルのfacebookSnカラム
  • Twitter         :Contact_テーブルのtwitterSnカラム
  • OPEN ID      :User_テーブルのopenIdカラム



表示タブ

最後の表示設定タブについてみていきましょう。
表示設定タブには

  • 通知設定
  • 表示設定

の2つの項目があります。


【通知設定】
ユーザの通知設定情報については
AnnouncementsDeliveryテーブル
+----------------+---------------+-------+--------+----------+--------+
| Field           | Type           | Null | Key   | Default | Extra |
+----------------+---------------+-------+--------+----------+--------+
| deliveryId   | bigint(20)    | NO   | PRI   | NULL    |          |
| companyId  | bigint(20)    | YES  | MUL | NULL    |          |
| userId         | bigint(20)    | YES  | MUL | NULL    |          |
| type_          | varchar(75) | YES  |        | NULL    |          |
| email          | tinyint(4)    | YES  |        | NULL    |          |
| sms            | tinyint(4)     | YES  |        | NULL    |          |
| website      | tinyint(4)     | YES  |        | NULL    |          |
+----------------+---------------+-------+--------+---------+---------+
に設定されています。
表示設定の項目で
共通は"general"
ニュースは"news",
テストポートレットは"test"
がtype_に設定されます。

【表示設定】
標準時についてはUser_テーブルのtimezoneIdカラム
あいさつ文についてはUser_テーブルのgreetingカラム
に設定されています。

以上で、ユーザ情報の設定項目に対応するデータベースのテーブル情報について
確認できました。
ユーザに関連する情報だけでも、さまざまなテーブルが利用されていることがわかりましたね。
Liferayではデータの追加・削除はデータベースを直接操作するのが推奨されていない理由が体感していただけたのではないでしょうか。

それでは、次回はまた別の設定項目についてみていきたいと思います。


Monday, June 22, 2020

Liferayでアクセス解析ツールを設定してみよう

こんにちは。大谷です。

Webサイトの運用にあたっては、どのページがよく見られているか、流入や離脱はどのようになっているのかなどのアクセス解析を通じて、ユーザー行動や成果を可視化し、Webサイトの改善につなげていきたいですよね。一般的にはGoogle Analyticsをはじめとするアクセス解析ツールが用いられますが、Liferayで作成したWebサイトについてもこれらのアクセス解析ツールを利用することができます。

そこで今回は、Liferayで構築したサイトへのアクセス解析ツールの設定方法を紹介したいと思います。大きく3つの方法があるので、それぞれについて説明します。

1. Google AnalyticsのトラッキングIDを指定する


Liferayは、Google AnalyticsのトラッキングIDを指定するだけでトラッキングコードを埋め込むことができます。設定方法は以下のとおり、とても簡単です!

1. こちらのドキュメントを参考にしてGoogle Analyticsをセットアップし、トラッキングIDを入手する。
2. Liferayにログインしてアクセス解析対象のサイトに移動し、「設定」->「サイト設定」->「詳細設定」タブを開く。
3. 「Google アナリティクスID」に先ほど入手したトラッキングIDを入力して「保存」をクリックする。

これでトラッキングコード埋め込み完了です。Google Analyticsでアクセス解析できるようになります。以下、2点ほど注意点があります。

  • 設定はサイト毎に行う必要があります。多数のサイトをアクセス解析対象とする場合は3.の方法を検討した方がよいかもしれません。
  • 埋め込まれるタグはGoogleアナリティクスタグ(analytics.jsスニペット)です。グローバルサイトタグ(gtag.jsスニペット)を埋め込みたい場合は2.の方法を検討してください。

2. アクセス解析ツールのトラッキングコード(タグ)を埋め込む


また、Liferayでは指定したアクセス解析用トラッキングコードをそのまま埋め込むこともできます。例えば、Matomo(旧Piwik)やAdobe AnalyticsなどGoogle Analytics以外のアクセス解析ツールを利用したり、Google Analyticsでもグローバルサイトタグを埋め込みたい場合はこの方法が便利です。

1. 利用するアクセス解析ツールをセットアップし、トラッキングコードを入手する。
2. Liferayに管理者でログインし、「コントロールパネル」->「設定」->「インスタンス設定」->「その他」タブを開く。
3. 「分析」欄にアクセス解析ツールの名前を追記して「保存」をクリックする(名前は区別がつけばよいだけなので適当でOK。以下の例では「gtag.js」を追加)。

4. アクセス解析対象のサイトに移動し、「設定」->「サイト設定」->「詳細設定」タブを開く。
5. 先ほど追加した項目にトラッキングコードを入力して「保存」をクリックする。

これでトラッキングコードが埋め込まれます。1.の方法と同様、サイト毎に設定を行う必要があります。

3. カスタムテーマにトラッキングコード(タグ)を埋め込む


多数のサイトに一括でトラッキングコードを埋め込みたい場合は、カスタムテーマにトラッキングコードを記述し、カスタムテーマを適用した全てのサイトにトラッキングコードを埋め込むという方法もあります。
他にも埋め込むべきタグがある場合や、タグ埋め込み先を詳細に管理したい場合などは、タグマネージャを利用する方がよいかもしれません。タグマネージャで埋め込むタグや配信先を管理するイメージです。具体的な方法は Liferayのサイトにタグを埋め込む の「全てのページにタグを埋め込む」で紹介していますのでご参照ください。


今回の記事は以上になります。アクセス解析ツールには無償利用可能なものもありますので、是非試してみてください。

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