Building Multi-Tenant APEX Apps
https://blog.cloudnueva.com/multi-tenant-apex-apps
イタリアのRoberto Capancioniさんも記事を書いています。
Multitenant App with APEX
Multitenant App with APEX
気になったので、自分でも使ってみようと思います。
Oracle APEXはアプリケーション・コンテキストのネームスペースとしてAPEX$SESSIONを用意しています。SYS_CONTEXT関数を呼び出して、ネーム・スペースAPEX$SESSIONよりAPP_ID、APP_SESSION、APP_USERといった値を参照できます。
SYS_CONTEXT関数から参照できる、APEXの組み込み置換文字列についてはマニュアルに記載があります。アプリケーション・コンテキストとして置換文字列の値を参照することには、いくつかの利点があります。
- V関数よりも高速である。V関数はPL/SQLによって実装されており、SELECT文にV関数が含まれていると、SQLとPL/SQLの実行エンジン間でのコンテキスト・スイッチが発生する。
- バインド変数と異なり、ビューを定義するSELECT文に含めることができる。
仮想プライベート・データベースにより同様の実装が可能ですが、その際は自前でネームスペースを作成する必要があります。APEX_SESSION.SET_TENANT_IDは、テナントの分離にパラメータを1つしか使わず、仮想プライベート・データベースまでは実装したくない、といった場合に手軽に活用できます。
簡単に言うと、APEX_SESSION.SET_TENANT_ID(p_tenant_id)を呼び出して設定した文字列は、SYS_CONTEXT('APEX$SESSION','APP_TENANT_ID')から参照できます。
サンプル・データセットのEMP/DEPTに含まれる表EMPを使って、APEX_SESSION.SET_TENANT_IDを使ったアプリケーションを作ってみます。
テナントを分離するパラメータとして列DEPTNOを使用します。列ENAMEの名前でアプリケーションにサインインすると、所属部門が同じ従業員(列DEPTNOの値が同じ)に限り参照できるようにします。
アプリケーション作成ウィザードを起動し、表EMPをソースとした対話モード・レポートのページを追加します。
追加する対話モード・レポートのページ名はEMPとします。表またはビュー、対話モード・レポートを選択し、表またはビューとしてEMPを選択します。フォームを含めるにチェックを入れます。
以上の設定でアプリケーションを作成します。
APEX_SESSION.SET_TENANT_IDは、サインイン時に実行される認証スキームの認証後のプロシージャ内で呼び出します。サインインの処理が完了した後にAPEX_SESSION.SET_TENANT_IDを呼び出し、APP_TENANT_IDを変更することはできません。
表EMPの列ENAMEの値でサインインする認証スキームを作成します。その認証スキームの認証後のプロシージャよりAPEX_SESSION.SET_TENANT_IDを呼び出し、APP_TENANT_IDを設定します。
共有コンポーネントの認証スキームを開きます。
作成済みの認証スキームが一覧されます。作成をクリックします。
次に進みます。
作成する認証スキームの名前は任意ですが、今回はENAMEでサインインとしました。スキーム・タイプとして公開資格証明を選択します。これは主にテストに使用する、ユーザー名だけでサインインする認証スキームです。
認証スキームが作成され、カレントの認証スキームになります。
ソースのPL/SQLコードに以下を記述します。サインインしたユーザ名で表EMPを検索し、見つかった従業員の部門コード(列DEPTNOの値)をAPP_TENANT_IDに設定しています。
プロシージャset_tenantをログイン・プロセスの認証後のプロシージャ名に記述します。
対話モード・レポートの列DEPTNOが数値で表示されるよう、識別のタイプをプレーン・テキストに変更します。
アプリケーションを実行します。
公開資格証明のサインインが要求されるので、SCOTTでサインインします。
今の所、アプリケーション・コンテキストのAPP_TENENT_IDは設定されていますが、アプリケーション側に何も制限はかかっていません。そのため表EMPのすべての行が表示されます。
SCOTTのDEPTNOは40で、同じ部門の従業員はMARTINのみです。
対話モード・レポートにAPP_TENANT_IDを使った制限を加えます。
ソースのWHERE句に以下の条件を記述します。
DEPTNO = SYS_CONTEXT('APEX$SESSION','APP_TENANT_ID')
APP_TENANT_IDを使って検索結果を制限することができましたが、表EMPがデータ・ソースに現れる度にWHERE句を追加するのは大変です。
表EMP(列DEPTNOを持つ表)を扱う際には、常にAPP_TENANT_IDで制限されるようにビューEMP_SVを作成します。そして表EMPの代わりにビューEMP_SVを指定することにより、アプリケーションのどこでも所属している部門のデータだけが扱えるように制限します。
create or replace view emp_sv
as
select empno, ename, job, mgr, hiredate, sal, comm from emp
where deptno = sys_context('APEX$SESSION','APP_TENANT_ID');
ビューEMP_SVは列DEPTNOを含んでいません。このビューに新規行を挿入すると、このままでは列DEPTNOが空白になります。列DEPTNOにAPP_TENANT_IDの値が設定されるよう、トリガーを作成します。
以上で準備は完了です。
ページ作成ウィザードを使って、ビューEMP_SVをデータ・ソースとした対話モード・レポートとフォームのページを作成します。
対話モード・レポートを選択します。
ページ定義のフォーム・ページを含めるにチェックを入れます。データ・ソースの表/ビューの名前としてEMP_SVを指定します。
次へ進みます。
主キー列1としてEMPNO(Number)を選択します。
ページの作成をクリックします。
以上の操作により、ビューEMP_SVをデータ・ソースとした対話モード・レポートとフォームのページが作成されます。
表EMPをデータ・ソースとした場合と、手順に違いはありません。
対話モード・レポートおよびフォームの操作からは、扱えるデータが制限されていることは分かりません。新規行を作成していますが、トリガーによってAPP_TENANT_IDが列DEPTNOに設定されているため、作成した行がレポートに表示されます。
APEXアプリケーションでのAPEX_SESSION.SET_TENANT_IDの活用は以上になります。
ビューEMP_SVに適用している条件がアクセス制限として適切なのかどうかを、APEXアプリケーションにアクセスして検証するのはあまり現実的ではありません。
APEXではコマンド・ラインの接続よりAPEXのセッションを作成するAPIとして、APEX_SESSION.CREATE_SESSIONを提供しています。このAPIの引数p_call_post_authenticationにtrueを渡すことにより、今回APEX_SESSION.SET_TENANT_IDを呼び出すために記述した認証後のプロシージャが実行されます。
これらの機能を使って、コマンド・ラインからビューEMP_SVを検索することができます。
SQL> begin
2 apex_session.create_session(
3 p_app_id => 222,
4 p_page_id => 1,
5 p_username => 'SCOTT',
6 p_call_post_authentication => true
7 );
8 end;
9* /
PL/SQLプロシージャが正常に完了しました。
SQL> select * from emp_sv;
EMPNO ENAME JOB MGR HIREDATE SAL COMM
________ _________ ____________ _______ ___________ _______ _______
7788 SCOTT PRESIDENT 7566 82-12-16 4000 300
7654 MARTIN CLERK 7698 81-09-28 1250 1400
SQL>
色々な条件のテスト・スクリプトを準備することにより、APP_TENANT_IDによるデータの分離が適切かどうかユーザー・インターフェースへアクセスせずに検証することができます。
本記事は以上になります。
今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/sample-set-tenant-id.zip
https://github.com/ujnak/apexapps/blob/master/exports/sample-set-tenant-id.zip
Oracle APEXのアプリケーション作成の参考になれば幸いです。
完