2022年1月25日火曜日

Flows for APEXによる経費精算アプリの作成(0) - はじめに

 Flows for APEXを使った経費精算を行なうアプリケーションを作成します。

以下の記事で構成されます。

0. はじめに - この記事。準備作業について
1. フロー・モデルの作成
2. ワークフローの実装
3. レーンとロールのを使ったアクセス制御
4. フロー・モデルのバージョニング
5. フロー・モデルのエクスポート
6. アプリケーションの更新
10. コール・アクティビティ

Flows for APEXを使うための準備については、休暇申請のアプリケーションを作る記事で解説しています。Flows for APEXのインストール方法や基本的な使い方は、休暇申請の記事を参照してください。

英語のTutorialは、MG GmbHのFlows for APEXのページよりダウンロードすることができます。ダウンロードされたZIPファイルには以下の3つのファイルが含まれています。

  • Tutorial_Flows_for_APEX_v22.2.pdf
  • tuto_db_objects.sql
  • tuto_expense_pkg.sql

本記事は英語のTutorialを元にしていますが、同じ内容ではありません。表の定義、APEXアプリケーション、フロー・モデルも微妙に異なります。また、英語のTutorialに含まれているEmail templatesに関する記述については触れていません。


最初に、経費精算アプリの雛形となるアプリケーションを作成します。

経費を保持する表TUTO_EXPENSESをクイックSQLを使って作成します。

SQLワークショップユーティリティクイックSQLを開きます。表を作成するスクリプトは以下になります。
# prefix: tuto
expenses /colprefix expe
    invoice_dd date /nn
    amount num      /nn
    purpose vc50
    justification vc150
    status vc15     /nn
    account num
    submitted_by vc30 /default coalesce(sys_context('apex$session','app_user'),sys_context('userenv','os_user'),sys_context('userenv','session_user')) /nn
    submitted_on tstz /default systimestamp /nn
    comment_mgr vc1000
    comment_vp vc1000
SQLの生成SQLスクリプトの保存レビューおよび実行を順番にクリックして表を作成します。


SQLのレビューおよび実行の画面で、EXPE_SUBMITTED_BY、EXPE_SUBMITTED_ONのデフォルト値が不要な ' (アポストロフィ)で囲まれているので削除します

SQLの修正後、実行をクリックします。


即時実行します。


表が作成されたことを確認し、アプリケーションの作成を実行します。


続けて、アプリケーションの作成を実行します。


アプリケーション作成ウィザードが起動します。

アプリケーションの名前経費精算とします。機能アクセス制御チェックを入れます。それ以外の機能は使用しないので、チェックを外します。

デフォルトで作成対象となるフォーム付き対話モード・レポートは使用しません。削除するために、編集をクリックします。


削除をクリックします。


アプリケーションの作成を実行します。


経費精算を行なうアプリケーションの雛形が出来上がります。

アプリケーション定義の編集をクリックし、アプリケーションの別名を設定します。


アプリケーションの別名EXPENSECLAIMとし、変更の適用をクリックします。


経費精算のアプリケーションも以前の記事にある休暇申請のアプリと同様に、アプリケーション・アイテムとしてPROCESS_IDSUBFLOW_IDおよびSTEP_KEYを使います。

共有コンポーネントアプリケーション・アイテムを開き、アプリケーション・アイテムを作成します。


名前PROCESS_IDセッション・ステート保護制限なしとして、アプリケーション・アイテムの作成を実行します。


同様の手順にて、アプリケーション・アイテムSUBFLOW_IDSTEP_KEYを作成します。

アプリケーション・アイテムPROCESS_IDSUBFLOW_IDおよびSTEP_KEYが作成されたことを確認します。


続いて、Flows for APEXを使うAPEXアプリケーションに必要なプラグインを、Flows for APEXのアプリケーションからコピーします。

共有コンポーネントプラグインを開きます。


作成をクリックします。


プラグインの作成として既存のプラグインのコピーを選択します。へ進みます。


アプリケーションからコピーとしてFlows for APEXを選択します。

へ進みます。


リージョン・プラグインのFlows for APEX - Modelerを除いて(これはいいえ)、それ以外ははいを選択します。プラグインのコピーを実行し、4つのプラグインを経費精算のアプリにコピーします。


コピーされたプラグインを確認します。


この経費精算のAPEXアプリケーションで扱うFlows for APEXのフローは、経費精算という名前で作成します。このフローの名前をコンポーネントのデフォルトになるよう設定します。

共有コンポーネントコンポーネント設定を開きます。


Flows for APEX - Manage Flow Instance [プラグイン]を開きます。


Global Flowとして経費精算を設定します。変更の適用をクリックします。


このように設定することにより、Flows for APEXのプラグインを元にプロセスを作成したときに、Select Flow usingComponent SettingFlow (Diagram) selection based onNameと設定すると、コンポーネントのデフォルト(Global Flowとして定義されている経費精算)が使用されます。以下は、プロセスの設定の画面例です。


以上でFlows for APEXを使ってワークフローを実装するアプリケーションの準備は完了しました。

次の記事では、Flows for APEXのアプリケーションを実行して、経費精算のフロー・モデルとダイアグラムを作成します。

2022年1月17日月曜日

DBのRSA暗号の互換性の確認

 RSA暗号がOracle Database 21c(19cにはバックポート)のDBMS_CRYPTOパッケージに追加されています。

DBMS_CRYPTO.PKENCRYPT/PKDECRYPTで暗号化/復号する、Javaに関してはjavax.crypto.Cipherで暗号化/復号するのは問題なくできます。また、DBMS_CRYPTO.SIGN/VERIFYでの署名/検証、java.security.Signatureによる署名/検証も同様です。

DBMS_CRYPTO.PKENCRYPTによる暗号化、javax.crypto.Cipherによる復号、またはその逆、およびDBMS_CRYPTO.SIGNによる署名、java.security.Signatureによる検証、またはその逆の互換性について確認した作業について記載します。


署名と検証


DBMS_CRYPTO.SIGNおよびVERIFYがサポートしている署名のハッシュ・アルゴリズムはマニュアルの以下に記載されています。

https://docs.oracle.com/en/database/oracle/oracle-database/21/arpls/DBMS_CRYPTO.html#GUID-E33DC872-3C26-4E7F-85F8-4D865FE73805

Table 47-22 SHA Hash AlgorithmsまたはTable 47-24 SHA Hash Algorithmsにリストがあります。

JavaのSignatureクラスで利用できる標準のアルゴリズムは以下にリストされています。



検証に使用したスクリプトは以下になります。DBとJavaの実装で、同じ公開鍵、秘密鍵を使用するため、JavaはOracle Databaseに実装されているJavaを使っています。DBMS_CRYPTOパッケージは秘密鍵のフォーマットとして、PKCS#1とPKCS#8の形式の両方をサポートしていたので、Javaで扱いやすいPKCS#8のフォーマットを採用しています。

PL/SQLのテスト・スクリプト


Javaによる署名と検証の実装


暗号化と復号


DBMS_CRYPTO.PKENCRYPT/PKDECRYPTがサポートしているアルゴリズムはPKENCRYPT_RSA_PKCS1_OAEPのみで詳しい仕様について記載がありません。


テスト・スクリプトをJavaで記述し暗号化/復号して確認したところ、ハッシュ・アルゴリズムとしてSHA-256が使用されていることが確認できています。

以下のスクリプトでテストしています。

PL/SQLのテスト・スクリプト


Javaによる暗号化と復号の実装


検証コードを書いていて気がついた点を以下に記載します。Javaのコードですが、最初は以下のようにjavax.crypto.Cipherを初期化していました。

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");

    PublicKey  publicKey  = keyFactory.generatePublic(new X509EncodedKeySpec(pkdata));

    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");

    cipher.init(Cipher.ENCRYPT_MODE, publicKey);


この方法で初期化するとデータベースで暗号化したテキストをJavaで復号、またはその逆はできませんでした。javax.crypto.BadPaddingExceptionが発生します。

成功した初期化手順は以下になります。PSource.PSpecified.DEFAULTの指定が必要になります。

    MGF1ParameterSpec mgf  = new MGF1ParameterSpec(alg);

    OAEPParameterSpec spec = new OAEPParameterSpec(alg,"MGF1",mgf,PSource.PSpecified.DEFAULT);

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");

    PublicKey  publicKey  = keyFactory.generatePublic(new X509EncodedKeySpec(pkdata));

    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");

    cipher.init(Cipher.ENCRYPT_MODE, publicKey, spec);


以上になります。

2022年1月14日金曜日

APEXアプリケーションを比較している実装を確認する

 Oracle APEXのアプリケーション・ビルダーは、アプリケーションの比較という機能を提供しています。

この機能はワークスペース・ユーティリティを開いた画面の、右ペインのリストから呼び出すことができます。見つけにくい所にあり、存在を忘れがちな機能ではあります。

一体どういう実装でアプリケーションを比較しているのかを確認してみました。

以下に確認手順を記載します。

最初にこちらの記事にある手順にそって、アプリケーション・ビルダーの実装となるf4000.sqlをワークスペースにインポートします。

アプリケーションの比較をするページのページ番号を確認します。APEX 21.2では1300でした。


アプリケーション・ビルダーのページ番号1300を検索し、ページ・デザイナで開きます。


実行ボタンはP1300_GO動作アクションページの送信であることが分かります。そのため、ボタンの処理はプロセスとして実装されています。


プロセス・ビューを開き、ボタンP1300_GOが押されたときに実行されるプロセスを確認します。サーバー側の条件ボタン押下時が、P1300_GOとなっているプロセスRedirect to self (RP)が見つかります。動作タイプとして、ページまたはURL(リダイレクト)が定義されています。

リダイレクト先のページ番号は1300なので、ページの再表示になります。


レンダリング・ビューを確認するとApplication Comparisonという対話モード・レポートのリージョンが確認できます。以下のサーバー側の条件が設定されていて、比較するアプリケーションが設定されているときのみ、レポートが表示されるようになっています。

nvl(v('REQUEST'),'X') != 'NORUN' and nvl(:APEX_APPLICATION_1,'0') != '0' and nvl(:APEX_APPLICATION_2,'0') != '0'


レポートのソースとして、以下のSQL問合せが使用されています。


ビューAPEX_COMPONENT_SIGNATURESの検索が実質的な処理であることが分かります。

アプリケーション・ビルダーは、APEXがインストールされているスキーマをデフォルトの解析スキーマとします。APEX_COMPONENT_SIGNATURESにはスキーマが指定されていないので(今回はAPEX 21.2で確認しているため、)APEX_210200がAPEX_COMPONENT_SIGNATURESの所有者です。

名前がAPEX_で始まっているので、これがビューであるのは間違いありませんが、一応、APEX_COMPONENT_SIGNATURESのオブジェクト・タイプを確認します。

DBA権限を持ったユーザーで実行します。

SQL> select object_name, object_type from dba_objects where owner = 'APEX_210200' and object_name = 'APEX_COMPONENT_SIGNATURES';


OBJECT_NAME       OBJECT_TYPE

------------------------------ ------------------------------

APEX_COMPONENT_SIGNATURES      VIEW


SQL> 


ビューの定義を確認します。

SQL> set long 30000

SQL> set pages 1000 lines 1000 trims on trimo on

SQL> col text format a10000

SQL> set heading off 

SQL> select text from dba_views where owner = 'APEX_210200' and view_name = 'APEX_COMPONENT_SIGNATURES';


select page_id,

       page_name,

       component,

       component_name,

       component_signature,

       sum(decode(application_id,x.a1,1,0)) as application_1,

       sum(decode(application_id,x.a2,1,0)) as application_2

  from

       (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x,

       (

select 'PAGE' component,

      page_id,

      page_name,

      lpad(page_id,5,'00000')||'. '||page_title component_name,

      component_signature,

      application_id

  from APEX_APPLICATION_PAGES,

      (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x

where application_id in (x.a1,x.a2)

  and (nvl(x.c,'0')='0' or x.c='PAGE')

union all

select 'PAGE.VALIDATION' component,

      page_id,

      page_name,

      VALIDATION_NAME component_name,

      component_signature,

      application_id

  from APEX_APPLICATION_PAGE_VAL,

      (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x

where application_id in (x.a1,x.a2)

  and (nvl(x.c,'0')='0' or x.c='PAGE.VALIDATION')

union all

select 'PAGE.BRANCH' component,

      page_id,

      page_name,

      nvl(branch_name, substr(BRANCH_ACTION,1,40)||length(branch_action)) component_name,

      component_signature,

      application_id

  from APEX_APPLICATION_PAGE_BRANCHES,

      (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x

where application_id in (x.a1,x.a2)


[中略]


  from apex_appl_plugins,

      (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x

where application_id in (x.a1, x.a2)

  and (nvl(x.c,'0')='0' or x.c='PLUGIN')

union all

select 'TEXT.MESSAGE' component,

      0 as page_id,

      null as page_name,

      translatable_message as component_name,

      component_signature,

      application_id

  from apex_application_translations,

      (select nv('APEX_APPLICATION_1') a1, nv('APEX_APPLICATION_2') a2, v('APEX_COMPONENT') c from sys.dual) x

where application_id in (x.a1, x.a2)

  and (nvl(x.c,'0')='0' or x.c='TEXT.MESSAGE')

       ) d

 group by page_id, page_name, component, component_signature, component_name



SQL> 


ビューの定義をファイルに落として内容を確認すると、それぞれのコンポーネントごとに列COMPONENT_SIGNATURESを比較してunion allしていることが分かります。

$ grep select view.sql | grep component

        select 'PAGE' component,

        select 'PAGE.VALIDATION' component,

        select 'PAGE.BRANCH' component,

        select 'PAGE.COMPUTATION' component,

        select 'PAGE.PROCESS' component,

        select 'PAGE.REGION' component,

        select 'PAGE.ITEM' component,

        select 'PAGE.BUTTON' component,

        select 'AUTHENTICATION' component,

        select 'LOV.ENTRY' component,

        select 'LOV' component,

        select 'APPLICATION.PROCESS' component,

        select 'APPLICATION.COMPUTATION' component,

        select 'APPLICATION.SETTING' component,

        select 'NAVBAR' component,

        select 'TAB' component,

        select 'PARENT.TAB' component,

        select 'BREADCRUMB' component,

        select 'LIST' component,

        select 'LIST.ENTRY' component,

        select 'TREE' component,

        select 'SHORTCUT' component,

        select 'APPLICATION.ITEM' component,

        select 'BUILD.OPTION' component,

        select 'BREADCRUMB.ENTRY' component,

        select 'APPLICATION.ATTRIBUTES' component,

        select 'PAGE.REGION' component,

        select 'REPORT.COLUMN' component,

        select 'REPORT.COLUMN' component,

        select 'AUTHORIZATION' component,

        select 'ACL.ROLE' component,

        select 'THEME' component,

        select 'TEMPLATE.CALENDAR' component,

        select 'TEMPLATE.BUTTON' component,

        select 'TEMPLATE.POPUP' component,

        select 'TEMPLATE.REPORT' component,

        select 'TEMPLATE.BREADCRUMB' component,

        select 'TEMPLATE.LIST' component,

        select 'TEMPLATE.LABEL' component,

        select 'TEMPLATE.REGION' component,

        select 'TEMPLATE.PAGE' component,

        select 'WEB.SERVICE' component,

        select 'DATA.LOADING' component,

        select 'PLUGIN' component,

        select 'TEXT.MESSAGE' component,

$ 


ビューAPEX_COMPONENT_SIGNATURES自体もですが、ビュー定義にWWV_FLOW_STEPSといった保護されているオブジェクトが含まれているため、一般のユーザーからはビューの検索やこのSQLの実行はできません。オンプレの環境であれば権限を与えればアクセスできますが、Autonomous DatabaseのAPEXの場合は不可です。

実装を確認できるだけでも、同様の機能を検討しているときには、有益な情報になるでしょう。

今回の記事は以上です。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2022年1月11日火曜日

APEXビューの比較をするPL/SQLスクリプト

 先日、APEXのリポジトリに対する変更の有無を確認するスクリプトを書いてみました。少々気になったので、それぞれのビューの変更点を見つけるスクリプトの書き方について、調べてみました。

ビューの親子関係の情報(これはビューAPEX_DICTIONARYから得られる)と、行を一意で認識する列の情報(これは簡単に得る方法はない - ただ、ビューの定義を見ると分かる)は必要でした。

以上になります。

Oracle APEXのアプリケーション開発の参考になれば幸いです。

2022年1月8日土曜日

APEXのリポジトリの変更を確認する

 Oracle APEXのアプリケーションが変更されていないことを確認するには、どのような方法があるでしょう?と相談を受けたので、ちょっと考えてみました。

Oracle APEXのアプリケーションを構成しているページ、リージョン、ページ・アイテムなどはすべてAPEXアプリケーション・ビューを通して情報を得ることができます。

アプリケーション・ビルダーApplication Expressビューから参照できます。

ビューからは、アプリケーションを構成している情報以外にも、稼働ログ、ワークスペース、登録されているユーザーやロールなども参照できます。

以下の手順で、APEXの変更を検知してみます。

  1. APEXの標準ビューの内容を実表にコピーする。
  2. コピーした実表とAPEXの標準ビューを比較する。
APEXの標準ビューをコピーするために、以下のPL/SQLスクリプトを書きました。


実行すると、APEX_ で始まるビューを、B001_で始まる表にコピーします。CTAS(CREATE TABLE AS SELECT)を実行しています。

続いて作成した表とAPEX標準ビューを比較します。以下のPL/SQLスクリプトを書きました。


APEXのアプリケーションは何も変更せずに上記のスクリプトを実行すると、以下の結果が返りました。
something changed B001_ACTIVITY_LOG
something changed B001_WORKSPACES
something changed B001_WORKSPACE_ACCESS_LOG
something changed B001_WORKSPACE_LOG_SUMMARY
something changed B001_WORKSPACE_LOG_SUMMARY_USR
something changed B001_WORKSPACE_SESSIONS

変更が検知されたのはログが書き込まれたり、利用状況によって更新される値を含んでいるビューです。変更の検知を確認するために、APEX標準ビューから表へコピーする対象に含めていましたが本来は対象から外すべきです。

このままでは結構な負荷になるため、ログ以外にも色々なビューを比較の対象から外す必要はあります。とはいえ、APEXアプリケーションの変更を検知する実装としての方向性は正しいと思います。データの変更を検知できているためです。

作成した表を削除するために、以下のスクリプトを書きました。


また、ビューAPEX_DICTIONARYよりビューの親子関係を確認できます。Application Expressビューのツリー・ビューの元になっている情報です。


上記の階層構造を検索しているSQLを簡略化すると、以下になります。


親となっているビューAPEX_APPLICATIONSへの行の追加、削除(更新は除く)については、APEX_APPLICATIONSを親としているビューに含まれている詳細情報を確認する必要はありません。それらはアプリケーションが追加されている場合は、詳細情報もすべて追加になります。アプリケーションが削除された場合は詳細情報もすべて削除されています。

データベースのディクショナリ、ビューAPEX_DICTIONARY共に、どれがユニークなカラムを示す情報がありません。そのため、挿入、削除、更新があったことは検出できますが、挿入、削除、更新のどの操作があったのか、特に更新の検出が困難です。

それぞれのビューのユニークな列を示す情報を別に用意する必要があります。

Oracle APEXを利用する際の参考になれば幸いです。