ラベル Tips の投稿を表示しています。 すべての投稿を表示
ラベル Tips の投稿を表示しています。 すべての投稿を表示

2021年1月9日土曜日

ORDS RESTfulサービスをOracle APEXのセッションで認証する

Twitterを見ていたら、このツィートへのKris Riceの返信として、ORDSのRESTfulサービスでOracle APEXのセッションを引き継げるとコメントしていました。

これは初耳だったので、自分でも確認してみました。

こちらの記事 - Oracle APEXでWebコンポーネント扱う - で作成したRESTfulサービスを保護した上で、アプリケーションを改変してみます。

RESTfulサービスの保護


RESTfulサービス自体はサンプルのoracle.example.hrです。このモジュールを対象として権限を追加します。マニュアルの記載箇所はこちらです。

SQLワークショップよりRESTfulサービスを開いて、左ペインのツリーから権限を選択します。右側のペインにORDSの権限として、一覧ページが表示されます。権限の作成を実行します。

名前、タイトルは任意です。ここではそれぞれ、名前をモジュール名と同じoracle.example.hrタイトルHR Accessとしています。

ロールとして、RESTful Servicesを選択します。そして、保護されたモジュールとして、oracle.example.hrを選択し、権限を作成します。


以上でRESTfulサービスの保護は完了です。

最初の例題で使用しているORDSテンプレート定義(employees/)の完全なURLを確認し、そのURLをブラウザから呼び出して、保護の状態を確認します。


URLにアクセスすると、401 Unauthrized が返されます。



アプリケーションの改変


保護されたRESTfulサービスへアクセスする際に、HTTPヘッダーとしてApex-Sessionを与えます。形式は以下です。

Apex-Session: アプリケーションID,セッションID

Webコンポーネントの記事で作成したアプリケーションをアプリケーション・ビルダーで開き、共有コンポーネント静的アプリケーション・ファイルを開きます。Edit Filesより、すでに作成済みのemployee-info.jsを選択します。


fetchの部分を、リクエストのヘッダーとしてApex-Sessionを含めるよう、以下に変更します。
    fetch('https://apex.oracle.com/pls/apex/あなたのWS/hr/employees/' + this.getAttribute('empno'),
      {
        "method": "GET",
        "headers": {
          "Apex-Session": this.getAttribute('appid') + "," + this.getAttribute('sessid')
        }
      }
    )

アプリケーションIDおよびセッションIDは、employee-info要素の属性のappidsessidとして渡します。

静的コンテンツのソースには、以下のようにappid、sessidを追加します。

<employee-info empno="7839" appid="&APP_ID." sessid="&APP_SESSION."></employee-info>


動的アクションの記載は、以下のようにappid、sessidを追加します。

$('#employee_data_container').append('<employee-info empno="' + empNo + '" appid="&APP_ID." sessid="&APP_SESSION."></employee-info>');


以上でアプリケーション側の変更も完了です。アプリケーションを実行すると、RESTfulサービスを保護する以前と同様に動作することが確認できます。

最後にKris Riceが、以下のSQLを実行していました。

select :current_user from dual

引き渡されたOracle APEXのセッションの認証ユーザー名(APP_USERに該当)が:current_userで取得できるとのこと。結果を見るとユーザー名が小文字になっていて、大抵の場合、大文字だけになるOracle APEX側のAPP_USER置換文字列とは若干異なるもようです。この点は要注意です。

2021年1月8日金曜日

CKEditor5でのリンク属性にtarget, relを付加する

追記: APEX 23.1よりリッチ・テキスト・エディタはCKEditor5からTinyMCEへ置き換えられます。CKEditor5のカスタマイズ方法はTinyMCEには適用できません。

---- 

Oracle APEX 20.2よりリッチ・テキスト・エディタがCKEditor5に変わりました。それに伴いHTMLの直接編集機能が無くなっています。

これ自体はクロスサイト・スクリプティングの脆弱性を回避する観点から、良いことなのですが、リンク(Aタグ)をつけた時に別ウィンドウで開けなくなった、HTMLを編集できないからtarget属性を付けることができない、どうすればいい?という質問がありました。

CKEditor5については、FOEX GmbHのStefan Dobreの良い記事 A closer look at the Rich Text Editor of #orclAPEX 20.2 というのがあり、それを参照しながら、対応を実装してみました。

以下のJavaScriptをJavaScript初期化コードに記述して、CKEditor5のオプションを設定すると、target="_blank" rel="noopener noreferrer"がAタグの属性として付加されます。

function(options) {
  options.editorOptions.link = { addTargetToExternalLinks: true };
  return options;
}

ページ・アイテムP1_TEXTがリッチ・テキスト・エディタです。

これで対応が完了です。確認のために1ページだけのアプリケーションを作りました。

CKEditor5も大量のAPIがあって色々なことができるようです。時間を見つけて機能を確認したいコンポーネントのひとつです。

2021年1月7日木曜日

Oracle APEXアプリケーションのデプロイメントに使用できる設定の紹介

幸運なことに作成したOracle APEXのアプリケーションを使う人が増え、色々機能を追加していくと、次第にページが増えていきます。ある程度ページが増えてくると、アプリケーションを更新するのが辛くなります。

基本的にOracle APEXアプリケーションのデプロイメントの単位はアプリケーションなので、それに大量のページが含まれていると、その内の1ページだけの変更であっても、エスクポートは全ページで、インポートも全ページの置き換えになります。また、変更が1ページであっても、それに伴って共有コンポーネントも変更しているかもしれませんし、改変したアプリケーションと現行のアプリケーションとの差分を、稼働しているアプリケーションに適用する、というのも難しい作業です。置き換える単位がアプリケーションであれば、テストの単位もアプリケーションとなるのが通常でしょう。

本記事では、上記のような難しさを軽減するために、比較的容易に採用できる方法について紹介します。項目は以下になります。

  1. 例題アプリケーションの作成
  2. アプリケーションの分割
  3. セッションの共有
  4. ホーム・ページ、ログイン・ページの共有
  5. ナビゲーションの更新
  6. 共有コンポーネントのサブスクリプション
  7. 別名の付け替えと可用性の設定
  8. その他の利用可能な機能

最初に例題となるアプリケーションを作成し、そのアプリケーションに対して、ここでリストした作業を実施していきます。

例題アプリケーションの作成

SQLワークショップのサンプル・データセットより、プロジェクト・データをインストールし、それを元にアプリケーションを作成します。

SQLワークショップユーティリティに含まれるサンプル・データセットを開きます。

プロジェクト・データインストールをクリックします。

データセットの説明が表示されます。へ進みます。

インストールされるデータセットがリストされます。確認してデータセットのインストールを実行します。

ロード結果が表示されます。そこからアプリケーションの作成を起動します。

言語日本語(ja)に変更し、それ以外はそのままにしてアプリケーションの作成を行います。

以上で例題にするアプリケーションが完成しました。

アプリケーションの分割

作成されたアプリケーションを以下に示すように、アプリケーションの別名dm-projects-001と別名dm-projects-002の、2つのアプリケーションに分割します。

Home, Dashboard, Statuses, Project Statusはdm-projects-001、Projects, Milestones, Tasks Project Task, Task Calendarはdm-projects-002、それ以外のページは双方のアプリケーションに残します。

作成されているアプリケーションをコピーします。タスクからこのアプリケーションのコピーを実行します。

新規アプリケーション名Demonstration - Projects - Homeとします。サポートするオブジェクトの定義のコピーは不要なので、OFFです。へ進みます。

確認画面からアプリケーションのコピーを実行します。

これで同一のアプリケーションが、ワークスペースに2つ存在することになります。最初にアプリケーションDemonstration - Projects - Homeをアプリケーション・ビルダーで開き、別名dm-projects-001と設定します。これからの説明は、簡易URLはONとOFFのどちらでも有効ですが、簡易URLがサポートされているバージョンであればONにするべきでしょう。設定ののち、変更の適用をクリックします。

このアプリケーションから、ページ3, 4, 5, 6, 7を削除します。ページ・デザイナにて、ひとつひとつページを開いて、削除していきます。

カスケード削除の確認を要求された場合は、いいえ - 対応するリスト・エントリを削除しないを選びます。そしてページを完全に削除を実行します。

アプリケーションdm-projects-001よりページを削除したら、次にアプリケーションDemonstration - Projectsを開き、別名dm-projects-002として設定し、こちらからはページ1, 2, 8, 9を削除します。

以上でアプリケーションの分割は完了です。次から、分割したアプリケーションを一体で動作するように設定を行なっていきます。

セッションの共有

それぞれのアプリケーションをアプリケーション・ビルダーで開き、共有コンポーネント認証スキームを開きます。

カレントの認証スキームを開き、セッション共有の設定を変更します。Cookie名任意の値(ここではORA_WWV_APP_CUSTOM_PROJECTSとしています)ですが、それ以外はOracle APEXをホストしているサーバーの環境によって決まる値ですので、ご自身の環境に合った値を設定してください。

  • タイプ: カスタム
  • Cookie名: ORA_WWV_APP_CUSTOM_PROJECTS
  • Cookieパス: /pls/apex/
  • Cookieドメイン: apex.oracle.com
  • セキュア: ON

分割したアプリケーションの両方に同じ設定を行うことで、クッキーに設定されるセッション識別子をアプリケーションで共有します。

ホーム・ページ、ログイン・ページの共有

今回はDemonstration - Projects - Home(別名dm-projects-001)のアプリケーションにホーム・ページを残しているため、別名dm-projects-002のアプリケーションは、ホーム・ページとログイン・ページとしてdm-projects-001のものを使うようにします。

dm-projects-002のアプリケーションのアプリケーション定義のユーザー・インターフェースを開き、属性ホームURLログインURLを変更します。

  • ホームURL: f?p=dm-projects-001:home:&SESSION.
  • ログインURL: f?p=dm-projects-001:LOGIN:&APP_SESSION.::&DEBUG.:::

&APP_ID.置換文字列は自分自身のアプリケーションIDを意味するので、これを分割したアプリケーションであるdm-projects-001へ置き換えます。また、ホームURLのページが1として数値で設定されていたので、別名のhomeに置き換えています。アプリケーションIDやページIDとして指定する部分には、できるだけ数値は使わない方がアプリケーションのメンテナンスが容易になります。

こちらについては、アプリケーションdm-projects-002だけの変更になります。アプリケーションに認証が必要なアクセスがあった場合、および、ホームページに移動する場合は、アプリケーションdm-projects-001へ遷移するようになります。

ナビゲーションの更新

ページ1, 2はアプリケーションdm-projects-001、ページ3, 4, 5, 6, 7はアプリケーションdm-projects-002をアクセスするよう、ナビゲーション・メニューを更新します。

共有コンポーネントリストを開きます。この中のデクストップ・ナビゲーション・メニューとページ・ナビゲーションを変更します。

他にも変更すべきリストがあるのですが、今回の例題の確認には影響がないため、そのままにします。

デスクトップ・ナビゲーション・メニューを開いて、グリッド編集をクリックします。


分割されたアプリケーションを指すエントリのターゲットに、アプリケーションの別名を指定します。

アプリケーションdm-projects-001では、以下のターゲットを変更します。HomeとDashboardはそのままでも大丈夫なのですが、ページ番号をページの別名に置き換えています。
  • Home: f?p=&APP_ID.:home:&APP_SESSION.::&DEBUG.:
  • Dashboard: f?p=&APP_ID.:dashboard:&APP_SESSION.::&DEBUG.:
  • Projects: f?p=dm-projects-002:projects:&APP_SESSION.::&DEBUG.:
  • Milestones: f?p=dm-projects-002:milestones:&APP_SESSION.::&DEBUG.:
  • Tasks: f?p=dm-projects-002:tasks:&APP_SESSION.::&DEBUG.:
  • Tasks Calendar: f?p=dm-projects-002:tasks-calendar:&APP_SESSION.::&DEBUG.:
アプリケーションdm-projects-002では、以下のターゲットを変更します。
  • Home: f?p=dm-projects-001:home:&APP_SESSION.::&DEBUG.:
  • Dashboard: f?p=dm-projects-001:dashboard:&APP_SESSION.::&DEBUG.:
  • Projects: f?p=&APP_ID.:projects:&APP_SESSION.::&DEBUG.:
  • Milestones: f?p=&APP_ID.:milestones:&APP_SESSION.::&DEBUG.:
  • Tasks: f?p=&APP_ID.:tasks:&APP_SESSION.::&DEBUG.:
  • Tasks Calendar: f?p=&APP_ID.:tasks-calendar:&APP_SESSION.::&DEBUG.:

ページ・ナビゲーションも同様に変更します。ページ・ナビゲーションはホーム・ページでのみ使用されるので、dm-projects-001だけ変更します。

以下のターゲットを変更します。

  • Dashboard: f?p=&APP_ID.:dashboard:&APP_SESSION.::&DEBUG.:
  • Projects: f?p=dm-projects-002:projects:&APP_SESSION.::&DEBUG.:
  • Milestones: f?p=dm-projects-002:milestones:&APP_SESSION.::&DEBUG.:
  • Tasks: f?p=dm-projects-002:tasks:&APP_SESSION.::&DEBUG.:
  • Tasks Calendar: f?p=dm-projects-002:tasks-calendar:&APP_SESSION.::&DEBUG.:

アプリケーションにたいして簡易URLを設定していても、ナビゲーションに指定するURLについてはf?p=形式のURLを使用できます。簡易URLの形式で指定するにはワークスペース名を含める必要があるなど、ナビゲーションの宛先URLとしては使いにくいのでf?p=形式で指定できるのは助かります。

ページの遷移にまつわる設定は以上で完了です。アプリケーションを実行して動作を確認してみます。

どちらのアプリケーションを実行しても、ログイン・ページはアプリケーションdm-projects-001に含まれるページが開きます。


ログインすると以下のエラーが発生しました。


コピーとして作成したアプリケーション(今回の手順ではdm-projects-001がコピー)の、共有コンポーネントアプリケーション・アクセス制御を開きます。


ユーザー・ロール割当ての追加を実行します。アプリケーション作成時に追加したアクセス制御の機能が有効な範囲はアプリケーション単体で、コピーしたアプリケーションやエクスポート/インポートしたアプリケーションにユーザー・ロールの割当てはコピーされません。


今現在ログインしているユーザー名を指定し、アプリケーション・ロールとして管理者コントリビュータにチェックを入れ、割当ての作成を行います。


 アプリケーション・ロールがユーザーに割当たったことを確認します。


アプリケーション・ロールを有効にするためには再度ログインする必要があります。エラーが表示されているページのURLよりsession=を取り除いてアクセスする、ブラウザを一旦閉じるなどして、セッションを初期化してログインし直します。

サイド・メニューやホーム・ページのナビゲーションをクリックして、アプリケーション自体も遷移すること、アクセスするアプリケーションが変わっても再認証が求められないことを確認します。



共有コンポーネントのサブスクリプション


共有コンポーネントはサブスクリプションの設定によって、異なるアプリケーションに作成されている共有コンポーネントの定義を流用することができます。

サブスクリプションの元となる共有コンポーネントを保持するためのアプリケーションを名前Demonstration - Project - Root別名dm-projects-rootとして作成します。dm-projects-001をコピーします。アプリケーションのコピーの手順、別名の設定については、すでに説明済みですので、そちらの手順に沿って行います。


他のアプリケーションからサブスクライブされている共有コンポーネントを保持しているアプリケーションは、入れ替えや削除を行うと影響が大きいので、エンドユーザーに提供する画面などを含まず、別のアプリケーションとすることを推奨します。今回は例題なので、作成したコピーからのページ削除の実施は省きます。

続いて、共有コンポーネントのLOVにサブスクリプションの設定を行なってみます。アプリケーションdm-projects-001をアプリケーション・ビルダーで開き、共有コンポーネントLOVを開き、EBA_PROJECTS.NAMEを開きます。


サブスクリプションとして、マスターLOVの参照元を設定します。リフレッシュにチェックが入っていると、変更を適用したときにマスターLOVの設定で上書きされます。


サブスクリプションの設定がされていると、リストにサブスクライブ元となるアプリケーションIDが表示されます。


再度、EBA_PROJECTS.NAMEを開きます。


マスターLOVおよび、それをサブスクライブしているLOVの双方で、それぞれLOVの設定を変更することが可能です。LOVのリフレッシュを実行するまでは、マスターLOVの設定で上書きされることはありません。

サブスクリプションをサポートしている共有コンポーネントでは、親となるアプリケーションにコンポーネントを作成し、そのアプリケーションからコンポーネントをサブスクライブするように設定することで、設定作業の冗長化を避けることができます。

別名の付け替えと可用性の設定


小さな単位に分割したアプリケーションのコピーを作成して、アプリケーションの改良といった作業を進めることもできます。

例えばアプリケーションdm-projects-001のコピーを作成し、Dashboardページを改良することができます。新規アプリケーション名はDemonstration - Projects - Newとします。


作成したアプリケーションのコピーは、別名としてdm-projects-001-newを設定します。

コピーしたアプリケーションを実行します。アプリケーションを実行してサイド・メニューやナビゲーションを辿ると別のアプリケーションへ遷移することがあるので注意が必要です。

以下のようにレイアウトを変えてみました。(Dashboardの改良自体が本題ではないので、どのように変更してもかまいません)。


アプリケーションの別名を入れ替えることにより、利用中のアプリケーションと更新したアプリケーションを交換することができます。

アプリケーションの定義を開き、別名dm-projects-001をdm-projects-001-oldに変更し、その後、dm-projects-001-newをdm-projects-001へ変更します。

先に、現在利用中のアプリケーションの別名を変更します。


続いて、機能を変更したアプリケーションの別名を変更します。


以上でアプリケーションの入れ替えが完了です。入れ替えたアプリケーションに問題があれば、別名の割り当てを元に戻せば、以前の状態に戻ります。(同時にバージョンやアプリケーション・グループなども更新すると、対応としてはより良くなります)。

このままの状態であれば、URLを意図的に指定することで、以前のアプリケーションと新しいアプリケーションの両方にアクセスできます。古いアプリケーションのアプリケーション定義を開き、可用性を定義することで、古いアプリケーションへのアクセスを禁止し、新しいアプリケーションの利用を強制することができます。

可用性ステータスとして使用不可(URLにリダイレクト)を選択し、使用できないアプリケーションに対するメッセージとして、リダイレクト先となるURLを指定します。


URLは簡易URLが使用される場合は簡易URLで、そうでない場合はf?p=の形式で指定します。リダイレクト先となるアプリケーションは別ワークスペースや別インスタンスのアプリケーションでも指定可能ですし、単なるHTTPのリダイレクト先ですので、Oracle APEXのアプリケーションでなくても指定できます。

ブックマークされたURLがすでに簡易URLの場合は、アプリケーションの指定は名称によるので、別名の入れ替えだけでブックマークはそのまま有効です。そのため、可用性の設定は必ずしも必要ではありません。簡易URLが導入される前のf?p=形式で、アプリケーションIDとして数値が使われている場合は、可用性の設定により新しいアプリケーションにリダイレクトさせることが可能になり、以前のブックマークにアクセスするとエラーが発生するといったことを抑制できます。

その他の利用可能な機能


アプリケーション・アイテム


アプリケーション・アイテムをアプリケーション間で共用するには、有効範囲グローバルとします。


APEXコレクション


APEXコレクションはアプリケーション間では共用できません。そのため分割したアプリケーション間でデータの受け渡しを行うには、APEXコレクションの代わりに実表を作るなどの対応が必要になります。

APEX_PAGE.GET_URL


ページ遷移のターゲットとなるURLの生成には、APEX_PAGE.GET_URLをできるだけ使用し、APEX_UTIL.PREPARE_URLの使用は避けます。APEX_UTIL.PREPARE_URLを使用してチェックサムをつけるには、引数として指定するURL(p_url)を、文字列として作る必要があります。

APEX_UTIL.HOST_URL


ターゲットを指定する完全なURLを作成する際にAPEX_PAGE.GET_URLが使えない場合は、APEX_UTIL.HOST_URLを使用し、サーバー名などの直書きを避けます。アプリケーションをコピーすることで別名を変更したり、エクスポート/インポートすることで開発環境と本番環境の間でアプリケーションをコピーしたときに、URLを直書きしていると手作業で修正しなければなりません。

毎回APEX_UTIL.HOST_URLを呼び出すことを避けるために、認証後や新規インスタンス(新規セッション)開始時に、アプリケーション・アイテムへアプリケーションの計算によって値を設定しておくこともできます。


ビルド・オプション


アプリケーションのテストに使用するページ、リージョンなど、本番利用時にはアクセス不可にするためにビルド・オプションが使えます。

例えば、以下のようにテスト中を示す静的コンテンツのリージョンをページに追加します。


共有コンポーネントのビルド・オプションを開きます。


ビルド・オプションとしてテストに使用ステータス含める、として作成します。


作成したリージョン(リージョンに限らず、ほとんどのコンポーネントにはビルド・オプションの指定があります)の構成ビルド・オプションとしてテストに使用を選択します。


これでテスト中です。と表示しているリージョンとビルド・オプションが紐づけられました。

ビルド・オプションステータス除外に変更します。変更を適用します。


この状態で最初のページを表示すると、リージョンが表示されないことが確認できます。


ビルド・オプションによる除外は、サーバー側の条件とは異なり、除外されたコンポーネントはアプリケーションに存在自体していない扱いになります。

ビルド・オプションのその他の機能やSQLclでのLiquibaseの使用など、より進んだOracle APEXのアプリケーションのデプロイメントについては、また記事を改めて紹介したいと思います。