検索

クリアフィルター
記事
Mihoko Iijima · 2021年8月23日

管理ポータルの InterSystems IRIS Business Intelligence 用メニュー:Analytics(または DeepSee)関連ページを開くとエラーになる場合の対処方法

これは InterSystems FAQ サイトの記事です。 InterSystems IRIS Business Intelligence 用メニューの Analytics(または DeepSee)を使用するためには、使用するネームスペース用ウェブアプリケーションパスを Analytics(またはDeepSee)に対応するように設定変更する必要があります。 詳細については以下のドキュメントをご参照ください。 【IRIS】InterSystems IRIS Business Intelligence の Web アプリケーション設定について 【2015.1~2018.1】DeepSee の Web アプリケーション設定について 【~2014.1】%CSPページへのアプリケーション・アクセスの制御について/特殊なケース: DeepSee 関連する FAQ トピックもご参照ください。 % 付き Web ページを開くとエラーになります。
お知らせ
Toshihiko Minamoto · 2022年6月4日

InterSystems IRIS、IRIS for Health、HealthShare Health Connect 2022.1がリリースされました!

インターシステムズは InterSystems IRIS Data Platform、InterSystems IRIS for Health、 HealthShare Health Connect がリリースされたことをお知らせします。 2022.1は、EMリリースですので、メンテナンス・ビルドが2年間利用でき、その後、さらに2年間、セキュリティに特化したビルドが利用できるようになります。 本リリースのハイライト プラットホームの更新 InterSystems IRIS Data Platform 2022.1 は以下の最新OSをサポートします。 Windows Server 2022 Windows 11 AIX 7.3 Oracle Linux 8 また、開発環境用として、M1 ならびに IntelチップセットのMacOS 12 (Monterey) をサポートします。 開発環境の向上 Embedded Python ... IRIS内でObjectScriptとPythonが使用できます。 Kafka,やAWS S3、AWS SNS、CloudWatch向けアダプタの追加 Production Extensions (PEX)のユーザインターフェースの更新 スピード、スケール、セキュリティ, Scale, & Security オンライン・シャード・リバランシング アダプティブ SQL ジャーナル、ストリーム圧縮 メールでのTLS 1.3, OAuth 2 サポート アナリティクス AI SQL ローダー InterSystems Reports デプロイメントの改良 これらの機能の詳細は製品ドキュメントをご覧ください InterSystems IRIS 2022.1 ドキュメント and リリースノート InterSystems IRIS for Health 2022.1 ドキュメント and リリースノート HealthShare Health Connect 2022.1 ドキュメント and リリースノート ソフトウェアの取得方法 このソフトウェアは従来のインストレーションパッケージとコンテナイメージの両方で取得できます。使用可能なインストレーションパッケージとコンテナイメージの全リストはSupported Platforms documentをご参照ください。 各製品の完全なインストレーションパッケージはWRCの Software Distribution page より取得できます。 カスタムインストレーションオプションを使用するとスタジオやIntegratedMLなど、必要なオプションを選択でき、インストールのフットプリントを適正なサイズにできます。 InterSystems IRIS and IRIS for Healthの Enterprise Edition、関連するコンポーネントのコンテナイメージは InterSystems Container Registry から以下のコマンドを使用して入手できます。 docker pull containers.intersystems.com/intersystems/iris:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/irishealth:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-arm64:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/irishealth-arm64:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-ml:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-ml-arm64:2022.1.0.209.0 利用可能なコンテナイメージのリストは ICR documentation をご覧ください。 Community Edition のコンテナイメージもInterSystems Container Registryから以下のコマンドで入手可能です。 docker pull containers.intersystems.com/intersystems/iris-community:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/irishealth-community:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-community-arm64:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/irishealth-community-arm64:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-ml-community:2022.1.0.209.0 docker pull containers.intersystems.com/intersystems/iris-ml-community-arm64:2022.1.0.209.0 InterSystems IRIS Studio 2022.1 はマイクロソフト Windows で動作する統合開発環境のコンポーネントで、WRCのコンポーネントダウンロードページ からダウンロードできます。 これはInterSystems IRIS and IRIS for Health version 2022.1 以下の環境で動作します。また、インターシステムズはマイクロソフトWindowsやLinux、MacOSで利用可能なVisual Studio Codeを使ってInterSystems IRISのアプリケーションを開発するためのVSCode-ObjectScript plugin をサポートします。 主なクラウドマーケットプレイスにおける弊社の対応リストは、数日中に更新される予定です。 このリリース版のビルド番号は、2022.1.0.209.0 です。
記事
Shintaro Kaminaka · 2020年7月3日

InterSystems IRIS Open Authorization Framework(OAuth 2.0)の実装 - パート1

この記事と後続の2つの連載記事は、InterSystems製品ベースのアプリケーションでOAuth 2.0フレームワーク(簡略化のためにOAUTHとも呼ばれます)を使用する必要のある開発者またはシステム管理者向けのユーザーガイドを対象としています。 作成者:Daniel Kutac(InterSystemsシニアセールスエンジニア) 公開後の修正および変更の履歴 2016年8月3日 - 新しいバージョンのページを反映するため、Googleのクライアント設定のスクリーンショットを修正し、Google APIのスクリーンショットを更新しました。 2016年8月28日 - Cache 2016.2でのJSON対応への変更を反映するため、JSON関連コードを変更しました。 2017年5月3日 - Cache 2017.1でリリースされた新しいUIと機能を反映するため、テキストと画面を更新しました。 2018年2月19日 - 最新の開発内容を反映するために、CachéをInterSystems IRISに変更しました。 製品名は変更されていますが、この記事はすべてのInterSystems製品(InterSystems IRIS Data Platform、Ensemble、Caché)を対象としています。 パート1. クライアント 概要 これは、3部構成のInterSystemsによるOpen Authorization Frameworkの実装に関する連載記事の最初の記事です。 この最初のパートでは、このトピックについて簡単に紹介し、InterSystems IRISアプリケーションが認証サーバーのクライアントとして機能し、保護されたリソースを要求する簡単なシナリオを示します。 パート2ではより複雑なシナリオについて説明します。そこではInterSystems IRIS自体が認証サーバーとして機能するほか、OpenID Connectを介した認証サーバーとしても機能します。 このシリーズの最後のパートでは、OAUTHフレームワーククラスの個々の部分について説明します。それらはInterSystems IRISにより実装されているからです。 Open Authorization Framework[1]とは 皆さんの多くはすでにOpen Authorization Frameworkとその使用目的について聞いたことがあるかと思います。 そのため、この記事では初めて同フレームワークを耳にした方のために簡単な要約を掲載します。 現在はバージョン2.0であるOpen Authorization Framework(OAUTH)は、クライアント(データを要求するアプリケーション)とリソース所有者(要求されたデータを保持するアプリケーション)の間に間接的な信頼を確立することにより、主にWebベースのアプリケーションが安全な方法で情報を交換できるようにするプロトコルです。 この信頼自体は、クライアントとリソースサーバーの両方が認識して信頼する機関によって提供されます。 この機関は認証サーバーと呼ばれます。 次の事例を使用して簡単に説明します。 Jenny(OAUTH用語ではリソース所有者)がJennyCorp社のプロジェクトに取り組んでいるとします。 彼女はより大きな潜在的なビジネスのプロジェクト計画を作成し、JohnInc社のビジネスパートナーであるJohn(クライアントユーザー)にドキュメントのレビューを依頼します。 ただし、彼女はジョンに自社のVPNへのアクセスを許可することを快く思っていないので、ドキュメントをGoogleドライブ(リソースサーバー)または他の同様のクラウドストレージに置いています。 そうすることで、彼女は彼女とGoogle(認証サーバー)の間に信頼関係を確立していました。 彼女はJohnと共有するドキュメントを選びます(JohnはすでにGoogleドライブを使用しており、Jennyは彼のメールアドレスを知っています)。 Johnはドキュメントを閲覧したいときには自分のGoogleアカウントで認証し、モバイルデバイス(タブレットやノートパソコンなど)からドキュメントエディタ(クライアントサーバー)を起動し、Jennyのプロジェクトファイルを読み込みます。 とてもシンプルに聞こえますが、2人とGoogleの間には多くの通信が発生しています。 どの通信もOAuth 2.0仕様に準拠しているため、Johnのクライアント(リーダーアプリケーション)は最初にGoogleで認証する必要があります(OAUTHはこのステップに対応していません)。ジョンがGoogleが提供するフォームにJohnが同意して認証すると、Googleはアクセストークンを発行し、リーダーアプリケーションにドキュメントへのアクセスを許可します。 リーダーアプリケーションはアクセストークンを使用してGoogleドライブにリクエストを発行し、Jennyのファイルを取得します。 以下の図に、個々の当事者間の通信を示しています。 注意: どのOAUTH 2.0通信もHTTPリクエストを使用していますが、サーバーは必ずしもWebアプリケーションである必要はありません。 InterSystems IRISを使ってこの簡単なシナリオを説明しましょう。 簡単なGoogleドライブのデモ このデモでは、私たち自身のアカウントを使ってGoogleドライブに保存されているリソース(ファイルのリスト)をリクエストする小さなCSPベースのアプリケーションを作成します(ついでにカレンダーのリストも取得します)。 前提条件 アプリケーションのコーディングを始める前に、環境を準備する必要があります。 この環境には、SSLが有効になっているWebサーバーとGoogleのプロファイルが含まれます。 Webサーバーの構成 上記のように、認証サーバーとはSSLを使用して通信する必要があります。これは、OAuth 2.0がデフォルトでSSLを要求するためです。 データを安全に保つためには必要なことですよね? この記事ではSSLをサポートするようにWebサーバーを構成する方法は説明しませんので、お好みの各Webサーバーのユーザーマニュアルを参照してください。 皆さんの好奇心をそそるため、この詳細な例ではMicrosoft IISサーバーを使用します(後でいくつかのスクリーンショットを掲載するかもしれません)。 Googleの構成 Googleに登録するには、Google API Manager(https://console.developers.google.com/apis/library?project=globalsummit2016demo)を使用する必要があります デモのために、GlobalSummit2016Demoというアカウントを作成しました。 Drive APIが有効になっていることを確認してください。 次に、認証情報を定義します。 次の点に注意してください。 承認済みのJavaScript生成元 – 呼び出し元のページに対し、ローカルで作成されたスクリプトのみを許可します。 承認済みのリダイレクトURI – 理論上はクライアントアプリケーションを任意のサイトにリダイレクトできますが、InterSystems IRISのOAUTH実装を使用する場合は https://localhost/csp/sys/oauth2/OAuth2.Response.cls にリダイレクトする必要があります。 スクリーンショットのように複数の承認済みのリダイレクトURIを定義できますが、このデモでは2つのうち2番目のエントリのみが必要です。 最後に、InterSystems IRISをGoogle認証サーバーのクライアントとして構成する必要があります。 Cachéの構成 InterSystems IRIS OAUTH2クライアントの構成は2段階で行われます。 まず、サーバー構成を作成する必要があります。 SMPで、System Administration(システム管理) > Security(セキュリティ) > OAuth 2.0 > Client Configurations(クライアント構成)を開きます。 「サーバー構成の作成」ボタンをクリックし、フォームに入力して保存します。 フォームに入力したすべての情報は、Google Developers Consoleのサイトで確認できます。 InterSystems IRISはOpen IDの自動検出に対応しています。 ただし、ここでは自動検出を使用せず、すべての情報を手動で入力しました。 次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成を作成する)ボタンをクリックします。 「Client Information」(クライアント情報)タブと「JWT Settings」(JWT設定)タブは空のままにし(デフォルト値を使用します)、クライアントの認証情報を入力してください。 注意:ここでは、Confidential Clientを作成しています。これはPublic Clientよりも安全であり、クライアントシークレットがクライアントサーバーアプリケーションを離れることはありません(ブラウザに送信されません)。 また、「Use SSL/TLS」(SSL/TLSを使用する)がチェックされ、ホスト名(ここではクライアントアプリケーションにローカルにリダイレクトしているため、localhostにします)が入力され、さらにはポートとプレフィックスが入力されていることを確認してください(これは同じマシンに複数のInterSystems IRISがある場合に役立ちます)。 入力した情報に基づいてクライアントリダイレクトURLが生成され、上の行に表示されます。 上のスクリーンショットでは、GOOGLEという名前のSSL構成を選択しました。 この名前自体は、多くのSSL構成のうちどれをこの特定の通信チャネルで使用するかを決定するためにのみ使用されます。 CachéはSSL/TLS構成を使用し、サーバー(この場合はGoogle OAuth 2.0 URI)との安全なトラフィックを確立するために必要なすべての情報を保存しています。 より詳細な説明については、ドキュメントを参照してください。 Googleの認証情報定義フォームから取得したクライアントIDとクライアントシークレットの値を入力します(手動構成を使用する場合)。 これですべての構成ステップが完了し、CSPアプリケーションのコーディングに進むことができます。 クライアントアプリケーション クライアントアプリケーションは、シンプルなWebベースのCSPアプリケーションです。 そのため、Webサーバーによって定義および実行されるサーバー側のソースコードと、Webブラウザによってユーザーに公開されるユーザーインターフェイスで構成されています。 クライアントサーバー クライアントサーバーは単純な2ページのアプリケーションです。 アプリケーション内では次の処理を実行します。 ·        Google認証サーバーのリダイレクトURLを組み立てます。 ·        Google Drive APIおよびGoogle Calendar APIへのリクエストを実行し、結果を表示します。 ページ1 これはアプリケーションの1ページであり、そのリソースについてGoogleを呼び出すことにしました。 以下はこのページを表す最小限の、しかし完全に動作するコードです。 Class Web.OAUTH2.Google1N Extends %CSP.Page { Parameter OAUTH2CLIENTREDIRECTURI = "https://localhost/csp/google/Web.OAUTH2.Google2N.cls"; Parameter OAUTH2APPNAME = "Google"; ClassMethod OnPage() As %Status { &html<<html> <head> </head> <body style="text-align: center;"> <!-- ページの内容をここに挿入します --> <h1>Google OAuth2 API</h1> <p>このページのデモでは、OAuth2認証を使用してGoogle API関数を呼び出す方法を示しています。 <p>ユーザーとユーザーのGoogleドライブのファイル、およびカレンダーエントリに関する情報を取得します。 > // Googleで認証するにはopenidのスコープを指定する必要があります set scope="openid https://www.googleapis.com/auth/userinfo.email "_ "https://www.googleapis.com/auth/userinfo.profile "_ "https://www.googleapis.com/auth/drive.metadata.readonly "_ "https://www.googleapis.com/auth/calendar.readonly" set properties("approval_prompt")="force" set properties("include_granted_scopes")="true" set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(..#OAUTH2APPNAME,scope, ..#OAUTH2CLIENTREDIRECTURI,.properties,.isAuthorized,.sc) w !,"<p><a href='"_url_"'><img border='0' alt='Googleサインイン' src='images/google-signin-button.png' ></a>" &html<</body> </html>> Quit $$$OK } ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { #dim %response as %CSP.Response set scope="openid https://www.googleapis.com/auth/userinfo.email "_ "https://www.googleapis.com/auth/userinfo.profile "_ "https://www.googleapis.com/auth/drive.metadata.readonly "_ "https://www.googleapis.com/auth/calendar.readonly" if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) { set %response.ServerSideRedirect="Web.OAUTH2.Google2N.cls" } quit 1 } } 以下にこのコードの簡単な説明を記します。 1.     OnPreHTTPメソッド - まず、すでに有効なアクセストークンをGoogleの認証結果として取得しているかどうかを確認します。この認証は、例えば単にページを更新したときに発生する可能性があります。 トークンを取得できていない場合は、認証する必要があります。 トークンを取得できている場合は、結果表示ページにページをリダイレクトするだけです。 2.      OnPageメソッド - 有効なアクセストークンがない場合にのみここに到達します。その場合、認証してGoogleに対する権限を付与し、アクセストークンを付与してもらうために通信を開始しなければなりません。 3.      Google認証ダイアログの動作を変更するスコープ文字列とプロパティ配列を定義します(私たちのIDに基づいて認証する前に、Googleに対して認証する必要があります)。 4.      最後にGoogleのログインページのURLを受け取り、それをユーザーに提示してから同意ページを表示します。 追加の注意事項: ここでは実際のリダイレクトページを https://www.localhost/csp/google/Web.OAUTH2.Google2N.cls(OAUTH2CLIENTREDIRECTURIパラメータ内)で指定しています。 ただし、Google認証情報の定義ではInterSystems IRIS OAUTH Frameworkのシステムページを使用しています。 リダイレクトは、OAUTHハンドラークラスによって内部的に処理されます。 ページ2 このページにはGoogle認証の結果が表示されます。成功した場合はGoogleのAPIコールを呼び出してデータを取得します。 繰り返しになりますが、このコードは最小限でも完全に機能します。 受信データがどのような構造で表示されるかは、皆さんのご想像にお任せします。 Include %occInclude Class Web.OAUTH2.Google2N Extends %CSP.ページ { Parameter OAUTH2APPNAME = "Google"; Parameter OAUTH2ROOT = "https://www.googleapis.com"; ClassMethod OnPage() As %Status { &html<<html> <head> </head> <body>> // アクセストークンがあるかどうかを確認します set scope="openid https://www.googleapis.com/auth/userinfo.email "_ "https://www.googleapis.com/auth/userinfo.profile "_ "https://www.googleapis.com/auth/drive.metadata.readonly "_ "https://www.googleapis.com/auth/calendar.readonly" set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) if isAuthorized { // Googleにはイントロスペクションエンドポイントがありませんので、呼び出す必要はありません。イントロスペクションエンドポイントと表示結果については、RFC 7662を参照してください。 w "<h3><span style='color:red;'>GetUserInfo API</span>からのデータ</h3>" // userinfoには専用のAPIがありますが、Get() メソッドを適切なURLで呼び出すだけでも取得できます。 try { set tHttpRequest=##class(%Net.HttpRequest).%New() $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME)) $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).GetUserinfo(..#OAUTH2APPNAME,accessToken,,.jsonObject)) w jsonObject.%ToJSON() } catch (e) { w "<h3><span style='color: red;'>エラー: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>" } /****************************************** * * * 他のAPIから情報を取得する * * * ******************************************/ w "<hr>" do ..RetrieveAPIInfo("/drive/v3/files") do ..RetrieveAPIInfo("/calendar/v3/users/me/calendarList") } else { w "<h1>認証されていません!</h1>" } &html<</body> </html>> Quit $$$OK } ClassMethod RetrieveAPIInfo(api As %String) { w "<h3><span style='color:red;'>"_api_"</span>からのデータ</h3><p>" try { set tHttpRequest=##class(%Net.HttpRequest).%New() $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME)) $$$THROWONERROR(sc,tHttpRequest.Get(..#OAUTH2ROOT_api)) set tHttpResponse=tHttpRequest.HttpResponse s tJSONString=tHttpResponse.Data.Read() if $e(tJSONString)'="{" { // JSONではない d tHttpResponse.OutputToDevice() } else { w tJSONString w "<hr/>" /* // 新しいJSON API &html<<table border=1 style='border-collapse: collapse'>> s tJSONObject={}.%FromJSON(tJSONString) set iterator=tJSONObject.%GetIterator() while iterator.%GetNext(.key,.value) { if $isobject(value) { set iterator1=value.%GetIterator() w "<tr><td>",key,"</td><td><table border=1 style='border-collapse: collapse'>" while iterator1.%GetNext(.key1,.value1) { if $isobject(value1) { set iterator2=value1.%GetIterator() w "<tr><td>",key1,"</td><td><table border=0 style='border-collapse: collapse'>" while iterator2.%GetNext(.key2,.value2) { write !, "<tr><td>",key2, "</td><td>",value2,"</td></tr>" } // このようにして埋め込みオブジェクト/配列をどんどん進めていきます w "</table></td></tr>" } else { write !, "<tr><td>",key1, "</td><td>",value1,"</td></tr>" } } w "</table></td></tr>" } else { write !, "<tr><td>",key, "</td><td>",value,"</td></tr>" } } &html<</table><hr/> > */ } } catch (e) { w "<h3><span style='color: red;'>エラー: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>" } } }     コードを簡単に見てみましょう。 1.      まず、有効なアクセストークンがあるかどうかを確認する必要があります(そのため、認証を受けました)。 2.      トークンがある場合はGoogleが提供し、発行されたアクセストークンがカバーするAPIにリクエストを発行できます。 3.       そのためには標準の %Net.HttpRequest クラスを使用しますが、呼び出されたAPIの仕様に従ってGETメソッドまたはPOSTメソッドにアクセストークンを追加します。 4.       ご覧のように、OAUTHフレームワークには GetUserInfo() メソッドが実装されていますが、RetrieveAPIInfo() ヘルパーメソッドの場合と同様に、Google APIの仕様を利用して直接ユーザー情報を取得できます。 5.       OAUTHの世界ではJSON形式でデータを交換するのが一般的であるため、ここでは受信データを読み取り、それを単にブラウザに出力してます。 受信データを解析して整形し、それをユーザーが理解できる形で表示できるようにするのはアプリケーション開発者の責任です。 しかし、それはこのデモの範囲を超えています。 (ただし、いくつかのコメントアウトされたコードで構文解析のやり方を示しています。) 以下は、未加工のJSONデータが表示された出力のスクリーンショットです。 パート2に進んでください。そこでは、認証サーバーおよびOpenID Connectプロバイダーとして機能するInterSystems IRISについて説明します。   [1] https://tools.ietf.org/html/rfc6749、https://tools.ietf.org/html/rfc6750
記事
Shintaro Kaminaka · 2020年8月26日

InterSystems IRIS Open Authorization Framework(OAuth 2.0)の実装 - パート3

作成者:Daniel Kutac(InterSystems セールスエンジニア)   パート 3. 付録 ## InterSystems IRIS OAUTH クラスの説明 この連載の[前のパート](https://jp.community.intersystems.com/node/480201)では、InterSystems IRIS を OAUTH クライアントおよび認可/認証サーバー(OpenID Connect を使用)として機能するように構成する方法について学びました。 この連載の最後のパートでは、InterSystems IRIS OAuth 2.0 フレームワークを実装するクラスについて説明します。 また、一部の API クラスのメソッドの使用例についても説明します。 OAuth 2.0 を実装する API クラスは、目的に応じて 3 種類のグループに分けることができます。 すべてのクラスは %SYS ネームスペースで実装されています。 これらの一部は(% package 経由で)公開されていますが、一部は非公開になっており、開発者が直接呼び出すことはできません。 ### 内部クラス これらのクラスは OAuth2 パッケージに属しています。 次の表に、対象となるクラスの一部を掲載しています(クラスの完全なリストについては、CachéあるいはIRIS インスタンスのオンラインクラスリファレンスを参照してください)。 以下の表に掲載されているものを除き、これらのクラスをアプリケーション開発者が直接使用することはできません。 クラス名 説明 OAuth2.AccessToken Persistent(永続クラス) OAuth2.AccessToken は、OAuth 2.0 アクセストークンとその関連情報を格納します。 これは、アクセストークンの OAUTH クライアントコピーです。OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。 OAuth2.Client Persistent(永続クラス) OAuth2.Application クラスは OAuth2 クライアントを記述し、RFC 6749 に基づいてアプリケーションを認可するために使用する認可サーバーを参照します。 クライアントシステムは、さまざまなアプリケーションで複数の認可サーバーと共に使用できます。 OAuth2.Response CSPページ これは、InterSystems IRIS OAuth 2.0 クライアントコードから使用される OAuth 2.0 認可サーバーからの応答用のランディングページです。 応答はここで処理され、最終的なターゲットにリダイレクトされます。 OAuth2.ServerDefinition Persistent(永続クラス) OAUTH クライアント(この InterSystems IRIS インスタンス)が使用する認可サーバーの情報が格納されています。 認可サーバーの定義ごとに複数のクライアント構成を定義できます。 OAuth2.Server.AccessToken Persistent(永続クラス) アクセストークンは OAUTH サーバーの OAuth2.Server.AccessToken によって管理されます。 このクラスには、アクセストークンと関連プロパティが格納されます。 このクラスは、認可サーバーのさまざまな要素間の通信手段でもあります。 OAuth2.Server.Auth CSP ページ 認可サーバーは、RFC 6749 で指定されている認可コードおよびインプリシットグラントタイプの認可制御フローをサポートします。 OAuth2.Server.Auth クラスは認可エンドポイントとして機能し、RFC 6749 に従ってフローを制御する %CSP.Page のサブクラスです。 OAuth2.Server.Client Persistent(永続クラス) OAuth2.Server.Configuration は、この認可サーバーに登録したクライアントを記述する永続クラスです。 OAuth2.Server.Configuration Persistent(永続クラス) 認可サーバーの構成が格納されます。 すべての構成クラスには、ユーザーが構成の詳細を入力するためのシステム管理ポータルページがそれぞれ存在します。 OAuth2.Client、OAuth2.ServerDefinition、OAuth2.Server.Client、OAuth2.Configuration の各オブジェクトは UI を使用せずに開き、変更し、作成または変更した構成を保存できます。 これらのクラスを使用し、構成をプログラムで操作できます。 ### サーバーカスタマイズ用クラス これらのクラスは %OAuth2 パッケージに属しています。 このパッケージには、一連の内部クラス(ユーティリティ)が含まれています。ここでは、開発者が使用できるクラスについてのみ説明します。 これらのクラスは、OAuth 2.0 Server Configuration(サーバー構成)ページで参照されます。 %OAuth2.Server.Authenticate CSPページ %OAuth2.Server.Authenticate はデフォルトの Authenticate クラスだけでなく、ユーザーが作成したすべての Authenticate クラスのサブクラスとして機能します。 Authenticate クラスは、ユーザーを認証するために OAuth2.Server.Auth の認可エンドポイントによって使用されます。 このクラスを使用すると、認証プロセスをカスタマイズできます。次のメソッドを OAuth2.Server のデフォルトをオーバーライドするために実装できます。 DirectLogin– ログインページを表示しない場合にのみ使用します。 DisplayLogin – 認可サーバーのログインフォームを実装します。 DisplayPermissions – リクエストされたスコープのリストを使用してフォームを実装します。CSS を変更することで、外観や操作性をさらにカスタマイズできます。 CSS スタイルは DrawStyle メソッドで定義されます。loginForm は DisplayLogin フォーム用です。permissionForm は DisplayPermissions フォーム用です。 %OAuth2.Server.Validate CSP ページ これは、サーバーに含まれているデフォルトの Validate User Class(ユーザー検証クラス)です。 デフォルトのクラスは、認証サーバーが配置されている Cache またはIRISインスタンスのユーザーデータベースを使用してユーザーを検証します。 サポートされるプロパティは、issuer(Issuer)、role、sub(Username)です。Validate User Class は Authorization Server Configuration(認可サーバーの構成)で指定されます。 ユーザー名とパスワードの組み合わせを検証し、ユーザーに関連付けられたプロパティ一式を返す ValidateUser メソッドを含める必要があります。 %OAuth2.Server.Generate Registered Object(登録オブジェクト) %OAuth2.Server.Generate は、サーバーに含まれているデフォルトのGenerate Token Class(トークン生成クラス)です。 デフォルトのクラスは、ランダムな文字列を opaque アクセストークンとして生成します。Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。 %OAuth2.Server.JWT Registered Object(登録オブジェクト) %OAuth2.Server.JWT は、サーバーに含まれている JSON Web トークンを作成する Generate Token Class です。 Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。 %OAuth2.Utils Registered Object(登録オブジェクト) このクラスは、さまざまなエンティティのログの記録を実装します。 カスタマイズの章のサンプルコードに、可能な使用法を示しています。 次の画像に、OAuth 2.0 認可サーバー構成の対応するセクションを示します。 ![](/sites/default/files/inline/images/1_7.png) OpenID Connect を JWT 形式の識別情報トークン(id_token)と共に使用する場合は、構成内のデフォルトの Generate Token Class である _%OAuth2.Server.Generate_ を _%OAuth2.Server.JWT_ に置換してください。それ以外の場合は、デフォルトの Generate クラスのままにしてください。 カスタマイズオプションについては、後で別の章で詳しく説明します。 ### 公開 API クラス 公開 API クラスは、アプリケーション開発者が Web アプリケーションのメッセージフローに正しい値を渡し、アクセストークンの検証やイントロスペクションなどを実行するために使用されます。 これらのクラスは %SYS.OAuth2 パッケージで実装されています。 次の表に、実装されているクラスの一部を掲載しています。 %SYS.OAuth2.AccessToken Registered Object(登録オブジェクト) %SYS.OAuth2.AccessToken クラスは、リソースサーバーへの認証にアクセストークンを使用できるようにするクライアント操作を定義します。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。 %SYS.OAuth2.Authorization Registered Object(登録オブジェクト) %SYS.OAuth2.Authorization クラスには、アクセストークンを取得してクライアントを認可するために使用される操作が含まれています。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。このクラスは CACHELIB にあるため、どこでも使用できることに注意してください。 ただし、トークンストレージは CACHESYS にあるため、ほとんどのコードでは直接使用できません。 %SYS.OAuth2.Validation Registered Object(登録オブジェクト) %SYS.OAuth2.Validation クラスは、アクセストークンの検証(または無効化)に使用されるメソッドを定義します。 基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。 このグループのいくつかのメソッドとクラスを詳しく見てみましょう。 アクセストークンを使用するすべてのクライアントアプリケーションクラスは、その有効性をチェックする必要があります。 このチェックは、OnPage メソッド(または ZEN か ZENMojo ページの対応するメソッド)のどこかで実行されます。 こちらがそのコードスニペットです。 // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。 set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1, scope2",.accessToken,.idtoken,.responseProperties,.error) // アクセストークンがあるかどうかをさらにチェックします。 // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。 // 各テストで返される JSON オブジェクトが単に表示されています。 if isAuthorized { // 何らかの処理を実行します – リソースサーバーの API を呼び出して目的のデータを取得します。 } リソースサーバーの API を呼び出すたびに、アクセストークンを提供する必要があります。 この処理は、_%SYS.OAuth2.AccessToken_ メソッドの **AddAccessToken** メソッドによって実行されます。こちらがそのコードスニペットです。 set httpRequest=##class(%Net.HttpRequest).%New() // AddAccessToken は現在のアクセストークンをリクエストに追加します。 set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken( httpRequest,, ..#SSLCONFIG, ..#OAUTH2APPNAME) if $$$ISOK(sc) { set sc=httpRequest.Get(.. Service API url …) } この連載の前のパートで提供したサンプルコードでは、最初のアプリケーションページ(Cache1N)の OnPreHTTP メソッドでこのコードを確認することができました。 このコードは、アプリケーションの最初のページでアクセストークンチェックを実行するのに最適な場所です。 ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { set scope="openid profile scope1 scope2" #dim %response as %CSP.Response if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,, scope,.accessToken,.idtoken,.responseProperties,.error) { set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls" } quit 1 } 上記のコードにある _SYS.OAuth2.AccessToken_ クラスの **IsAuthorized** メソッドは有効なアクセストークンが存在するかどうかをチェックし、存在しない場合に認可サーバーの認証フォームを指すログインボタン/リンクを使用してページの内容を表示し、存在する場合に実際にデータ取得処理を実行する 2 番目のページにリダイレクトします。 ただし、このコードは次のように変更できます。 ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { set scope="openid profile scope1 scope2" set sc=##class(%SYS.OAuth2.Authorization).GetAccessTokenAuthorizationCode( ..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties) quit +sc } この場合は、結果が異なります。 _%SYS.OAuth2.Authorization_ クラスの **GetAccessTokenAuthorizationCode** メソッドを使用すると、アプリケーションの最初のページの内容を表示せずに、認可サーバーの認証フォームに直接移動します。 これは、Web アプリケーションがモバイルデバイスのネイティブアプリケーションから呼び出され、一部のユーザー情報がネイティブアプリケーション(ランチャー)によってすでに表示されており、認可サーバーを指すボタンを含む Web ページを表示する必要がない場合に便利です。 署名付き JWT トークンを使用する場合は、その内容を検証する必要があります。 この検証は次のメソッドで実行されます。 set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(applicationName,accessToken,scope,,.jsonObject,.securityParameters,.sc) メソッドパラメーターの詳細については、Class Reference ドキュメントをご覧ください。 ## カスタマイズ OAUTH が認証 / 承認 UI のカスタマイズ用に提供しているオプションについてもう少し説明します。 勤務先のポリシーで、スコープの付与に関してより限定的な動作が要求されているとします。 たとえば、取引先銀行内のさまざまな金融システムに接続するホームバンキングアプリケーションを実行できるとしましょう。 銀行は、取得対象の実際の銀行口座に関する情報を含むスコープへのアクセスのみを許可します。 銀行は非常に多くの口座を運営しているため、すべての口座に静的なスコープを定義することは不可能です。 代わりに、認可処理中にその場でスコープを生成する処理をカスタム認可ページのコードに組み込むことができます。 デモを行うため、ここではサーバー構成にもう 1 つのスコープを追加する必要があります。次の画像を参照してください。 ![](/sites/default/files/inline/images/2_2.png) また、%OAuth2.Server.Authenticate.Bank という名前のカスタム Authenticate クラスへの参照も追加しました。 では、銀行の認証クラスはどのようになるのでしょうか? 次は想定されるクラスの例です。 このクラスは、ユーザーが提供するデータを使用して標準の認証フォームと認可フォームを拡張します。 **BeforeAuthenticate**、**DisplayPermissions**、**AfterAuthenticate** の各メソッド間を流れる情報は、_%OAuth2.Server.Properties_ クラスの _properties_ 変数によって渡されます。 Class %OAuth2.Server.Authenticate.Bank Extends %OAuth2.Server.Authenticate { /// account(口座)のスコープに CUSTOM BESTBANK のサポートを追加します。 ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { // 起動スコープが指定されていない場合は何もしません。 If 'scope.IsDefined("account") Quit $$$OK // 起動クエリパラメーターから起動コンテキストを取得します。 Set tContext=properties.RequestProperties.GetAt("accno") // コンテキストがない場合は何もしません。 If tContext="" Quit $$$OK try { // ここで BestBank コンテキストを照会する必要があります。 Set tBankAccountNumber=tContext // accno のスコープを追加します。 -> 動的にスコープを変更(account なし:<accno> スコープはサーバー構成に存在します) // この特定のスコープは、それが Cookie サポートを使用して account または account:accno によって // 以前に選択されていた場合に account 経由で同じ accno にアクセスできるようにするために使用されます。 Do scope.SetAt("Access data for account "_tBankAccountNumber,"account:"_tBankAccountNumber) // 処理が終わった account のスコープはもう必要ありません。 // これにより、account スコープが存在することで DisplayPermissions が強制的に呼び出されるのを防ぎます。 Do scope.RemoveAt("account") // AfterAuthenticate が応答プロパティに変換する accno プロパティを追加します。 Do properties.CustomProperties.SetAt(tBankAccountNumber,"account_number") } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } Quit $$$OK } /// account のスコープに CUSTOM BESTBANK のサポートを追加します。 /// account_number カスタムプロパティが BeforeAuthenticate(account)または /// DisplayPermissions(account:accno)によって追加された場合は、必要な応答プロパティを追加します。 ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { // account_number(account)または accno(account:accno)プロパティが存在しない限り、ここで実行することは何もありません。 try { // カスタムログを記録する例 If $$$SysLogLevel>=3 { Do ##class(%OAuth2.Utils).LogServerScope("log ScopeArray-CUSTOM BESTBANK",%token) } If properties.CustomProperties.GetAt("account_number")'="" { // 応答に accno クエリパラメーターを追加します。 Do properties.ResponseProperties.SetAt(properties.CustomProperties.GetAt("account_number"),"accno") } } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } Quit $$$OK } /// BEST BANK の account のテキストを含むように変更された DisplayPermissions ClassMethod DisplayPermissions(authorizationCode As %String, scopeArray As %ArrayOfDataTypes, currentScopeArray As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { Set uilocales = properties.RequestProperties.GetAt("ui_locales") Set tLang = ##class(%OAuth2.Utils).SelectLanguage(uilocales,"%OAuth2Login") // $$$TextHTML(Text,Domain,Language) Set ACCEPTHEADTITLE = $$$TextHTML("OAuth2 Permissions Page","%OAuth2Login",tLang) Set USER = $$$TextHTML("User:","%OAuth2Login",tLang) Set POLICY = $$$TextHTML("Policy","%OAuth2Login",tLang) Set TERM = $$$TextHTML("Terms of service","%OAuth2Login",tLang) Set ACCEPTCAPTION = $$$TextHTML("Accept","%OAuth2Login",tLang) Set CANCELCAPTION = $$$TextHTML("Cancel","%OAuth2Login",tLang) &html<<html>> Do ..DrawAcceptHead(ACCEPTHEADTITLE) Set divClass = "permissionForm" Set logo = properties.ServerProperties.GetAt("logo_uri") Set clientName = properties.ServerProperties.GetAt("client_name") Set clienturi = properties.ServerProperties.GetAt("client_uri") Set policyuri = properties.ServerProperties.GetAt("policy_uri") Set tosuri = properties.ServerProperties.GetAt("tos_uri") Set user = properties.GetClaimValue("preferred_username") If user="" { Set user = properties.GetClaimValue("sub") } &html<<body>> &html<<div id="topLabel"></div>> &html<<div class="#(divClass)#">> If user '= "" { &html< <div> <span id="left" class="userBox">#(USER)#<br>#(##class(%CSP.Page).EscapeHTML(user))#</span> > } If logo '= "" { Set espClientName = ##class(%CSP.Page).EscapeHTML(clientName) &html<<span class="logoClass"><img src="#(logo)#" alt="#(espClientName)#" title="#(espClientName)#" align="middle"></span>> } If policyuri '= "" ! (tosuri '= "") { &html<<span id="right" class="linkBox">> If policyuri '= "" { &html<<a href="#(policyuri)#" target="_blank">#(POLICY)#</a><br>> } If tosuri '= "" { &html<<a href="#(tosuri)#" target="_blank">#(TERM)#</a>> } &html<</span>> } &html<</div>> &html<<form>> Write ##class(%CSP.Page).InsertHiddenField("","AuthorizationCode",authorizationCode),! &html<<div>> If $isobject(scopeArray), scopeArray.Count() > 0 { Set tTitle = $$$TextHTML(" is requesting these permissions:","%OAuth2Login",tLang) &html<<div class="permissionTitleRequest">> If clienturi '= "" { &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>> } Else { &html<#(##class(%CSP.Page).EscapeHTML(clientName))#> } &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>> Set tCount = 0 Set scope = "" For { Set display = scopeArray.GetNext(.scope) If scope = "" Quit Set tCount = tCount + 1 If display = "" Set display = scope Write "<div class='permissionItemRequest'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>" } } If $isobject(currentScopeArray), currentScopeArray.Count() > 0 { Set tTitle = $$$TextHTML(" already has these permissions:","%OAuth2Login",tLang) &html<<div>> &html<<div class="permissionTitleExisting">> If clienturi '= "" { &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>> } Else { &html<#(##class(%CSP.Page).EscapeHTML(clientName))#> } &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>> Set tCount = 0 Set scope = "" For { Set display = currentScopeArray.GetNext(.scope) If scope = "" Quit Set tCount = tCount + 1 If display = "" Set display = scope Write "<div class='permissionItemExisting'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>" } &html<</div>> } /*********************************/ /* BEST BANK CUSTOMIZATION */ /*********************************/ try { If properties.CustomProperties.GetAt("account_number")'="" { // Display the account number obtained from account context. Write "<div class='permissionItemRequest'><b>Selected account is "_properties.CustomProperties.GetAt("account_number")_"</b></div>",! // or, alternatively, let user add some more information at this stage (e.g. linked account number) //Write "<div>Account Number: <input type='text' id='accno' name='p_accno' placeholder='accno' autocomplete='off' ></div>",! } } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } /* original implementation code continues here... */ &html< <div><input type="submit" id="btnAccept" name="Accept" value="#(ACCEPTCAPTION)#"/></div> <div><input type="submit" id="btnCancel" name="Cancel" value="#(CANCELCAPTION)#"/></div> > &html<</form> </div>> Do ..DrawFooter() &html<</body>> &html<<html>> Quit 1 } /// CUSTOM BESTBANK の場合、入力された患者を検証する必要があります。 /// ! このメソッドの javascript はユーザーに追加データを DisplayPermissions メソッド内 /// で入力させる場合にのみ必要です ! ClassMethod DrawAcceptHead(ACCEPTHEADTITLE) { &html<<head><title>#(ACCEPTHEADTITLE)#</title>> Do ..DrawStyle() &html< <script type="text/javascript"> function doAccept() { var accno = document.getElementById("accno").value; var errors = ""; if (accno !== null) { if (accno.length < 1) { errors = "Please enter account number name"; } } if (errors) { alert(errors); return false; } // submit the form return true; } </script> > &html<</head>> } } ご覧のとおり、%OAuth2.Server.Properties クラスにはいくつかの配列が含まれており、渡されています。 具体的には、以下の配列です。 ·        RequestProperties – 認可リクエストのパラメーターが含まれています。 ·        CustomProperties – 上記のコードの間でデータをやり取りするためのコンテナ。 ·        ResponseProperties – トークンリクエストに対する JSON 応答オブジェクトに追加されるプロパティのコンテナ。 ·        ServerProperties – 認可サーバーがカスタマイズコードに公開する共有プロパティが含まれます(logo_uri、client_uri など…) さらに、認可サーバーが返す必要のあるクレームを指定するのに使用されるいくつかの "claims" プロパティが含まれています。 この認証ページを正しく呼び出すため、最初のクライアントページのコードを次のように変更しました。 set scope="openid profile scope1 scope2 account" // このデータはアプリケーションに由来し(フォームデータなど)、リクエストのコンテキストを設定します。 // ここでは Authenticate クラスをサブクラス化することで、このデータをユーザーに表示することができます。 // それにより、該当ユーザーはアクセスを許可するかどうかを決めることができます。 set properties("accno")="75-452152122-5320" set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, scope, ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc) if $$$ISERR(sc) { write "GetAuthorizationCodeEndpoint Error=" write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",! } ご覧のとおり、ここではアプリケーションのさまざまな部分から発生する可能性のあるコンテキスト値を使用して account のスコープとプロパティ配列ノード “accno” を追加しました。 この値はアクセストークンの内部でリソースサーバーに渡され、さらに処理されます。 上記のロジックは、電子的な患者記録を交換するための FHIR 標準で実際に使用されています。 デバッグ OAUTH フレームワークには、デバッグ機能が組み込まれています。 クライアントとサーバー間の通信はすべて暗号化されているため、これは非常に便利です。 デバッグ機能を使用すると、API クラスによって生成されたトラフィックデータをネットワーク経由で送信する前にキャプチャできます。 コードをデバッグするため、以下のコードに従って単純なルーチンまたはクラスを実装できます。 InterSystems IRIS インスタンスのすべての通信でこのコードを実装する必要があることに注意してください! その場合、OAUTH フローのプロセス内での役割を示す名前をファイル名に指定することをお勧めします。 (以下のサンプルコードは rr.mac ルーチンとして保存されていますが、どんな名前を付けるかはあなた次第です。) // d start^rr() start() public { new $namespace set $namespace="%sys" kill ^%ISCLOG set ^%ISCLOG=5 set ^%ISCLOG("Category","OAuth2")=5 set ^%ISCLOG("Category","OAuth2Server")=5 quit } // d stop^rr() stop() public { new $namespace set $namespace="%sys" set ^%ISCLOG=0 set ^%ISCLOG("Category","OAuth2")=0 set ^%ISCLOG("Category","OAuth2Server")=0 quit } // display^rr() display() public { new $namespace set $namespace="%sys" do ##class(%OAuth2.Utils).DisplayLog("c:\temp\oauth2_auth_server.log") quit } 次に、テストを開始する前にターミナルを開き、すべての InterSystems IRIS ノード(クライアント、認可サーバー、リソースサーバー)で d start^rr() を呼び出してください。 完了後、d stop^rr() と d display^rr() を実行してログファイルを読み込んでください。 最後に この連載記事では、InterSystems IRIS OAuth 2.0 の実装を使用する方法を学びました。 パート1では簡単なクライアントアプリケーションのデモを行い、パート2では複雑な例を説明しました。 最後に、OAuth 2.0 の実装で最も重要なクラスについて説明し、ユーザーアプリケーション内でそれらを呼び出す必要がある場合について説明しました。 時々私が投げかけるくだらない質問に我慢強く回答し、この連載をレビューしてくれた Marvin Tener に心から感謝の意を表します。  
記事
Shintaro Kaminaka · 2020年8月20日

InterSystems IRIS Open Authorization Framework(OAuth 2.0)の実装 - パート2

作成者:Daniel Kutac(InterSystems セールスエンジニア) 注意: _*使用されている URL に戸惑っている方のために*。*元の連載記事では、dk-gs2016 と呼ばれるマシンの画面を使用していました。 新しいスクリーンショットは別のマシンから取得されています。 **WIN-U9J96QBJSAG という URL は dk-gs2016*_ であると見なしても構いません。 ### パート2. 認可サーバー、OpenID Connect サーバー この短い連載の[前のパート](https://jp.community.intersystems.com/node/478821)では、OAUTH[[1](#note1)] クライアントとして機能する単純な使用事例について学びました。 今回は私たちの経験をまったく新しいレベルに引き上げましょう。 InterSystems IRIS がすべての OAUTH の役割を果たす、より複雑な環境を構築します。 クライアントの作成方法はすでに分かっていますので、認可サーバーだけでなく、OpenID Connect[[2](#note2)] プロバイダーにも注意を向けましょう。 前のパートと同様に、環境を準備する必要があります。 今回はより多くの変動要素があるため、より注意を要します。 具体例を見る前に、OpenID Connect について少し説明する必要があります。 前のパートの内容を覚えていらっしゃるかと思いますが、Google から認可してもらうため、まずは自身がGoogle で認証を受けることを求められていました。 認証は OAUTH フレームワークには含まれていません。 実際、OAUTH に依存しない多くの認証フレームワークがあります。 そのうちの1つに OpenID と呼ばれるものがあります。 当初は単独の構想で開始されましたが、最近では OAUTH フレームワークによって提供されるインフラストラクチャ、つまり通信とデータ構造が活用されています。 その結果、OpenID Connect が誕生しました。 事実、多くの人がこれを OAUTH の強化版と呼んでいます。 実際、OpenID Connect を使用すると、認可するだけでなく、OAUTH フレームワークのよく知られたインターフェースを使用して認証することもできます。 # 複雑な OpenID Connect のデモ ここでは、パート 1 のクライアントコードの多くを活用します。 そうすることで多くの手間が省けるため、環境のセットアップに集中できます。 ## 前提条件 今回は、SSL が有効になっている既存の Web サーバーに PKI インフラストラクチャを追加する必要があります。 OpenID Connect の要件である暗号化が必要です。 ユーザー認証が必要な場合は、第三者がエージェント(クライアント、認証サーバーなど)になりすますし、ネットワーク経由でユーザーの機密データを送信できないようにする必要があります。 そこで X.509 ベースの暗号化の出番です。 注意 : Cache 2017.1 以降では、X.509 証明書を使用して JWT / JWKS(JSON Web Key Set)を生成する必要はありません。 ここでは下位互換性と単純化を図るためにこのオプションを使用しています。 ### PKI 厳密に言えば、Caché PKI インフラストラクチャを使用する必要はまったくありません。ただし、openssl などのツールを直接使用してすべての証明書を生成するよりは楽です。 詳細は InterSystems IRIS の[ドキュメント](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_pki#GCAS_C157792)や他の場所でも確認できますので、ここでは証明書の生成に関する詳細は触れません。 証明書を生成した結果、3 つの公開鍵/秘密鍵のペアと関連する証明書が作成されます。 これらを次のように呼びます。 * 発行元認証局の root_ca(root_ca.cer) * 認可サーバーおよび OpenID サーバー用の auth(auth.cer および auth.key) * クライアントアプリケーションサーバー用の client(client.cer および client.key) ### X.509 資格情報 デモ中に交換された JSON Web Token(JWT)に署名して検証できるよう、個々のサーバーで X.509 資格情報を定義する必要があります。 ### 認可サーバーと認証サーバーの構成 ここでは X.509 資格情報の定義方法については詳しく説明せず、AUTHSERVER インスタンスの資格情報のスクリーンショットを示します。 ![](/sites/default/files/inline/images/1_4.png) 画像のように、AUTHSERVER にはその秘密鍵と証明書がありますが、CLIENT に関しては公開鍵を含む証明書しかありません。 ### **クライアントサーバーの構成** 同様に、CLIENT インスタンスで資格情報が定義されています。 ![](/sites/default/files/inline/images/2_0.png) ここで、CLIENT には秘密鍵と証明書がありますが、AUTHSERVER に関しては公開鍵を含む証明書しかありません。 ### **リソースサーバーの構成** このセットアップの例では、RESSERVER インスタンスで X509 資格情報を定義する必要はありません。 ## **OAUTH の構成** この連載のパート 1 で説明した構成と同様に、サーバーを OAUTH 用に構成する必要があります。 まずは、OAUTH 構成全体の中心的なコンポーネントである AUTHSERVER インスタンスから始めましょう。 ### **AUTHSERVER** System Management Portal で、**System Administration > Security > OAuth 2.0 > Server Configuration** を開きます。 メニューのリンクをクリックし、次のフォーム項目に入力します。 * Host name(ホスト名) * Port(ポート、省略可) * Prefix(プレフィックス、省略可) – これら 3 つのフィールドは *Issuer endpoint*(発行者エンドポイント)を構成します。 * 更新トークンを返す条件を指定します。 * Supported grant types(サポートされているグラント種別)をチェックします。このデモでは 4 つの種別すべてにチェックします。 ただし、認可コードのみが使用されます。 * 必要に応じて Audience required(オーディエンスを要求)をチェックします。これにより、*aud* プロパティが認可コードと暗黙的な許可(Implicit)のリクエストに追加されます。 * 必要に応じて Support user session(ユーザーセッションをサポート)をチェックします。これにより、認可サーバーが現在このブラウザを使用しているユーザーのログイン状態を維持するために *httpOnly* Cookie が使用されます。 2 回目以降のアクセストークンの要求では、ユーザー名とパスワードの入力は求められません。 * Endpoint intervals(エンドポイントの間隔)を指定します。 * このサーバーでサポートされるスコープ(Supportted scopes)を定義します。 * Customization Options(カスタマイズオプション)のデフォルト値を受け入れるか、カスタム値を入力します。**注意: **JWT が単なる opaque トークンではなくアクセストークンとして使用されるよう、Generate token class(生成トークンクラス)の値を **%OAuth2.Server.Generate** から **%OAuth2.Server.JWT** に変更してください。 * OAuth 2.0 の要求に従い、HTTP 上の SSL を確立するための登録済み SSL 構成の名前を入力します。 * JSON Web Token(JWT)の設定を入力します。 以下はサンプル構成のスクリーンショットです。 ![](/sites/default/files/inline/images/4_4_1.png) ![](/sites/default/files/inline/images/5_3_0.png) ![](/sites/default/files/inline/images/6_2_1.png) ![](/sites/default/files/inline/images/7_0_1.png) ![](/sites/default/files/inline/images/8_0.png) サーバー構成を定義したら、サーバークライアント構成を入力する必要があります。 サーバー構成フォームのページ内で、「Client Configurations」(クライアント構成)ボタンをクリックし、CLIENT インスタンスおよび RESSERVER インスタンスの「Create New Configuration」(新しい構成の作成)をクリックします。 (IRISを使用して構成している場合は、「クライアントディスクリプション」の構成ページから以下の設定を行います。) 以下の画像は CLIENT の構成を示しています。 ![](/sites/default/files/inline/images/9_0.png) ![](/sites/default/files/inline/images/10_0.png) ![](/sites/default/files/inline/images/11_0.png) JWT Token(JWT トークン)タブはデフォルト値である空のままにします。 ご覧のとおり、実際のアプリケーションの場合とは異なり、フィールドには無意味なデータが入力されています。 同様に、RESSERVER の構成を示します。 ![](/sites/default/files/inline/images/12_0.png) ![](/sites/default/files/inline/images/13_0.png) ご覧のとおり、リソースサーバーに必要なのは非常に基本的な情報だけです。具体的には、Client type(クライアント種別)をリソースサーバーに設定する必要があります。 CLIENT では、より詳細な情報とクライアント種別を入力する必要があります(クライアントはクライアントシークレットをサーバーで維持し、クライアントエージェントには送信しない Web アプリケーションとして動作するため、機密(confidential)を選択します)。 ### **CLIENT** SMP で、**System Administration > Security > OAuth 2.0 > Client Configurations** を開きます。 「Create Server Configuration」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 ![](/sites/default/files/inline/images/14_0.png) Issuer Endpoint(発行者エンドポイント)が、AUTHSERVER インスタンスで前に定義した値に対応していることを必ず確認してください! また、認可サーバーのエンドポイントを Web サーバーの構成に従って変更する必要があります。 この場合は各入力フィールドに「authserver」を埋め込んだだけです。 次に、新しく作成された発行者エンドポイントの横にある「**Client Configurations**」(クライアント構成)リンクをクリックし、「**Create Client Configuration**」(クライアント構成の作成)ボタンをクリックします。 ![](/sites/default/files/inline/images/15_0.png) ![](/sites/default/files/inline/images/16.png) ![](/sites/default/files/inline/images/17.png) ![](/sites/default/files/inline/images/18.png) 以上です! ここまでの手順で CLIENT と AUTHSERVER の両方を構成しました。 多くの使用事例ではこれだけで十分です。リソースサーバーは単なる AUTHSERVER のネームスペースにすぎず、結果的にすでに保護されている場合があるからです。 しかし、外部の医師が内部の臨床システムからデータを取得しようとしている使用事例に対応する場合を考えてみましょう。 このような医師がデータを取得できるようにするため、この医師のアカウント情報を監査目的と法医学的な理由でリソースサーバー内に確実に保存したいと思います。 この場合は、続けて RESSERVER を定義する必要があります。 ### **RESSERVER** SMP で、**System Administration > Security > OAuth 2.0 > Client Configurations** を開きます。 「**Create Server Configuration**」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 ![](/sites/default/files/inline/images/19.png) ここでは、Cache 2017.1 に実装された新機能である検出機能を使用しました。 ご覧のように、この構成は CLIENT インスタンスの対応する構成と同じデータを使用しています。 次に、新しく作成された発行者エンドポイントの横にある「**Client Configurations**」(クライアント構成)リンクをクリックし、「**Create Client Configuration**」(クライアント構成の作成)ボタンをクリックします。 ![](/sites/default/files/inline/images/20.png) ![](/sites/default/files/inline/images/21.png) X.509 資格情報から JWT を作成することはお勧めしませんが、ここでは互換性のために使用しました。 ![](/sites/default/files/inline/images/22.png) CLIENTとほとんど同じ手順でしたが、必要なものでした。 しかし、これで先に進んでコーディングできるようになりました! ## **クライアントアプリケーション** 話をできる限り簡単にするため、パート1で説明した Google の例から多くのコードをリサイクルします。 クライアントアプリケーションは /csp/myclient で実行されるわずか 2 つの CSP ページからなるアプリケーションです。セキュリティは強制せず、未認証ユーザーとして実行されます。 ### **ページ 1** Class Web.OAUTH2.Cache1N Extends %CSP.Page { Parameter OAUTH2CLIENTREDIRECTURI = "https://dk-gs2016/client/csp/myclient/Web.OAUTH2.Cache2N.cls"; Parameter OAUTH2APPNAME = "demo client"; ClassMethod OnPage() As %Status { &html // 適切なリダイレクトとスコープを持つ認証エンドポイントの URL を取得します。 // 返された URL は下のボタンで使用されます。 // DK: 'dankut' アカウントを使用して認証します! set scope="openid profile scope1 scope2" set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, scope, ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc) if $$$ISERR(sc) { write "GetAuthorizationCodeEndpoint Error=" write ..EscapeHTML($system.Status.GetErrorText(sc))_"",! } &html< Authorize for ISC > Quit $$$OK } ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { #dim %response as %CSP.Response set scope="openid profile scope1 scope2" if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) { set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls" } quit 1 } } ### **ページ 2** Class Web.OAUTH2.Cache2N Extends %CSP.Page { Parameter OAUTH2APPNAME = "demo client"; Parameter OAUTH2ROOT = "https://dk-gs2016/resserver"; Parameter SSLCONFIG = "SSL4CLIENT"; ClassMethod OnPage() As %Status {     &html          // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。     set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1 scope2",.accessToken,.idtoken,.responseProperties,.error)          // アクセストークンがあるかどうかをさらにチェックします。     // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。     // 各テストで返される JSON オブジェクトが単に表示されています。     if isAuthorized {         write "Authorized!",!                           // JWT の場合、検証してからアクセストークンから詳細を取得します。         set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,accessToken,"scope1 scope2",,.jsonObject,.securityParameters,.sc)         if $$$ISOK(sc) {             if valid {                 write "Valid JWT"_"",!                 } else {                 write "Invalid JWT"_"",!                 }             write "Access token="             do jsonObject.%ToJSON()             write "",!         } else {             write "JWT Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!             }         write "",!         // イントロスペクションエンドポイントを呼び出して結果を表示します。RFC 7662 を参照してください。         set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection(..#OAUTH2APPNAME,accessToken,.jsonObject)         if $$$ISOK(sc) {             write "Introspection="             do jsonObject.%ToJSON()             write "",!         } else {             write "Introspection Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!             }         write "",!                  if idtoken'="" {             // ID トークンの検証と表示。OpenID Connect Core の仕様を参照してください。             set valid=##class(%SYS.OAuth2.Validation).ValidateIDToken(                 ..#OAUTH2APPNAME,                 idtoken,                 accessToken,,,                 .jsonObject,                 .securityParameters,                 .sc)             if $$$ISOK(sc) {                 if valid {                     write "Valid IDToken"_"",!                     } else {                     write "Invalid IDToken"_"",!                     }                 write "IDToken="                 do jsonObject.%ToJSON()                 write "",!             } else {                 write "IDToken Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!                 }         } else {             write "No IDToken returned"_"",!         }         write "",!              // アプリケーションロジックには不要ですが、委任認証に渡すことができるユーザーに関する情報を提供します。              // Userinfo エンドポイントを呼び出して結果を表示します。OpenID Connect Core の仕様を参照してください。         set sc=##class(%SYS.OAuth2.AccessToken).GetUserinfo(             ..#OAUTH2APPNAME,             accessToken,,             .jsonObject)         if $$$ISOK(sc) {             write "Userinfo="             do jsonObject.%ToJSON()             write "",!         } else {             write "Userinfo Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!             }         write "",!         /***************************************************         *                                                  *         *   リソースサーバーを呼び出し、結果を表示します。   *         *                                                  *         ***************************************************/                          // オプション 1 - リソースサーバー - 定義によれば認可サーバーからのデータを信頼します。         //     そのため、リソースサーバーに渡されたアクセストークンが有効である限り         //  要求元を問わずデータを提供します。                  // オプション 2 - または委任認証(OpenID Connect)を使用して          //  (委任認証を保護して)別の CSP アプリケーションを呼び出すこともできます。         //  - これはまさにこのデモで実施する内容です。                           write "リソースサーバーの呼び出し(委任認証)","",!         set httpRequest=##class(%Net.HttpRequest).%New()         // AddAccessToken は現在のアクセストークンをリクエストに追加します。         set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(             httpRequest,,             ..#SSLCONFIG,             ..#OAUTH2APPNAME)         if $$$ISOK(sc) {             set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio/oauth2test.demoResource.cls")         }         if $$$ISOK(sc) {             set body=httpRequest.HttpResponse.Data             if $isobject(body) {                 do body.Rewind()                 set body=body.Read()             }             write body,"",!         }         if $$$ISERR(sc) {             write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!             }         write "",!              write "Call resource server - no auth, just token validity check","",!         set httpRequest=##class(%Net.HttpRequest).%New()         // AddAccessTokenは現在のアクセストークンをリクエストに追加します。         set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(             httpRequest,,             ..#SSLCONFIG,             ..#OAUTH2APPNAME)         if $$$ISOK(sc) {             set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio2/oauth2test.demoResource.cls")         }         if $$$ISOK(sc) {             set body=httpRequest.HttpResponse.Data             if $isobject(body) {                 do body.Rewind()                 set body=body.Read()             }             write body,"",!         }         if $$$ISERR(sc) {             write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"",!             }         write "",!     } else {         write "Not Authorized!",!         write "Authorize me"     }         &html     Quit $$$OK } } 次のスクリーンショットで処理を説明します。 AUTHSERVER インスタンスの認可 / OpenID Connect 認証サーバーのログインページ ![](/sites/default/files/inline/images/11.png) AUTHSERVER のユーザー同意ページ ![](/sites/default/files/inline/images/12.png) その後、最終的に結果ページが表示されます。 ![](/sites/default/files/inline/images/13.png) ご覧のとおり、実際にコードを読んでもパート 1 で示したクライアントのコードとほとんど違いはありません。 ページ 2 には違いがあります。 これはデバッグ情報であり、JWT の有効性をチェックしています。 返ってきた JWT を検証した後、AUTHSERVER からのユーザー識別情報に関するデータに対してイントロスペクションを実行できました。 ここではこの情報をページに出力しただけですが、それ以上のこともできます。 上記で説明した外部の医師の使用事例と同様に、必要に応じて識別情報を使用し、それを認証目的でリソースサーバーに渡すことができます。 または、この情報をパラメーターとしてリソースサーバーへの API 呼び出しに渡すこともできます。 次の段落では、ユーザー識別情報の使用方法について詳しく説明します。 ## **リソースアプリケーション** リソースサーバーは認可 / 認証サーバーと同じサーバーにすることができ、多くの場合はそうなります。 しかし、このデモでは 2 つのサーバーを独立した InterSystems IRIS インスタンスにしました。 したがって、リソースサーバーでセキュリティコンテキストを使用する方法には 2 つあります。 ### **方法 1 – 認証なし** これは最も単純なケースです。 認可 / 認証サーバーはまったく同じ Caché インスタンスです。 この場合、単一の目的のために特別に作成された csp アプリケーションにアクセストークンを渡すだけで、OAUTH を使用するクライアントアプリケーションにデータを提供し、データを要求することを認可できます。 リソース csp アプリケーションの構成(ここでは /csp/portfolio2 と呼びました)は、以下のスクリーンショットのようになります。 ![](/sites/default/files/inline/images/14.png) 最小限のセキュリティをアプリケーション定義に組み込み、特定の CSP ページのみを実行できるようにします。 または、リソースサーバーは従来の Web ページの代わりに REST API を提供できます。 実際のシナリオでは、セキュリティコンテキストを微調整するのはユーザー次第です。 ソースコードの例: Class oauth2test.demoResource Extends %CSP.Page { ClassMethod OnPage() As %Status { set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc) if $$$ISOK(sc) { set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject) if $$$ISOK(sc) { // 必要に応じて jsonObject のフィールドを検証します w "Hello from Caché server: /csp/portfolio2 application!" w "running code as $username = "_$username_" with following $roles = "_$roles_" at node "_$p($zu(86),"*",2)_"." } } else { w "NOT AUTHORIZED!" w "" w i $d(%objlasterror) d $system.OBJ.DisplayError() w "" } Quit $$$OK } } ### **方法 2 – 委任認証** これはもう 1 つの極端なケースです。ユーザーがリソースサーバーの内部ユーザーと同等のセキュリティコンテキストで作業しているかのように、リソースサーバーでユーザーの識別情報を可能な限り最大限に活用したいと考えています。 解決方法の 1 つは、委任認証を使用することです。 この方法を実行するには、さらにいくつかの手順を実行してリソースサーバーを構成する必要があります。 ·        委任認証を有効にする ·        ZAUTHENTICATE ルーチンを提供する ·        Web アプリケーションを構成する(この場合、/csp/portfolio で呼び出しました) ここではユーザー識別情報とそのスコープ(セキュリティプロファイル)を提供した AUTHSERVER を信頼しているため、ZAUTHENTICATE ルーチンの実装は非常に単純で簡単です。そのため、ここではいかなるユーザー名も受け入れ、それをスコープと共にリソースサーバーのユーザーデータベースに渡しています(OAUTH スコープと InterSystems IRIS のロール間で必要な変換を行ったうえで)。 それだけです。 残りの処理は InterSystems IRIS によってシームレスに行われます。 これは ZAUTHENTICATE ルーチンの例です。 #include %occErrors #include %occInclude ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC { set tRes=$SYSTEM.Status.OK() try { set Properties("FullName")="OAuth account "_Username //set Properties("Roles")=Credentials("scope") set Properties("Username")=Username //set Properties("Password")=Password // Credentials 配列を GetCredentials() メソッドから渡せないため、一時的に書き換えます。 set Properties("Password")="xxx" // OAuth2 アカウントのパスワードは気にしません。 set Properties("Roles")=Password } catch (ex) { set tRes=$SYSTEM.Status.Error($$$AccessDenied) } quit tRes } GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public { s ts=$zts set tRes=$SYSTEM.Status.Error($$$AccessDenied) try { If ServiceName="%Service_CSP" { set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc) if $$$ISOK(sc) { set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject) if $$$ISOK(sc) { // ToDo: 標準アカウントと委任されたアカウント(OpenID)が競合する可能性があるため、注意してください! set Username=jsonObject.username set Credentials("scope")=$p(jsonObject.scope,"openid profile ",2) set Credentials("namespace")=Namespace // temporary hack //set Password="xxx" set Password=$tr(Credentials("scope")," ",",") set tRes=$SYSTEM.Status.OK() } else { set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed) } } } else { set tRes=$SYSTEM.Status.Error($$$AccessDenied) } } catch (ex) { set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed) } Quit tRes } CSP ページ自体は非常にシンプルになります。 Class oauth2test.demoResource Extends %CSP.Page { ClassMethod OnPage() As %Status { // アクセストークン認証は委任認証によって実行されます! // もう一度ここで行う必要はありません。 // これはリクエストからアクセストークンを取得し、イントロスペクションエンドポイントを // 使用してアクセストークンの有効性を確認するダミーのリソースサーバーです。 // 通常、応答はセキュリティに関連しませんが、リクエストパラメーターに基づく // 興味深いデータが含まれている可能性があります。 w "Hello from Caché server: /csp/portfolio application!" w "running code as $username = "_$username_" with following $roles = "_$roles_" at node "_$p($zu(86),"*",2)_"." Quit $$$OK } } そして最後に、/csp/portfolio の Web アプリケーション構成を示します。 ![](/sites/default/files/inline/images/15.png) あなたが本当に心配であったなら、最初のバリエーションで行ったように _Permitted クラス_を設定できたかもしれません。 または、REST API を使用していたかもしれません。 しかし、これらの設定に関してはこの記事の中では説明しません。 次回は、InterSystems IRIS OAUTH フレームワークによって導入される個々のクラスについて説明します。 それらの API、およびそれらを呼び出すタイミングと場所について説明します。   [1] この記事で OAUTH について言及する場合、常に RFC 6749()で規定されている OAuth 2.0 を指しています。 ここでは簡略化のために短縮形の OAUTH を使用しています。 [2] OpenID Connect は OpenID Foundation()によって管理されています。
お知らせ
Rie Tokue · 2025年5月28日

インターシステムズ 第3回 ソリューションウェビナー「サイロ化されたデータを有効活用するには? InterSystems Data Fabric Studioのご紹介 」のご案内

ご好評をいただいておりますソリューションウェビナー、3回目は以下のテーマで開催します。是非ご参加ください! 日時:6月17日(火)13時半~14時半 タイトル:サイロ化されたデータを有効活用するには? InterSystems Data Fabric Studioのご紹介 参加費無料・事前登録制 ご登録はこちらから 【概要】 あらゆるステークホルダーが必要な時に必要なデータにアクセスするには、まずアプリケーション毎に異なる種類のデータを使いやすい、統一されたデータ形式の変換が不可欠です。データアクセスに関わる課題を弊社の新製品、InterSystems Dat Fabric Studioはどのように解決できるのか、ユースケースとデモを交えてお話します。 【こんな方にお勧め】 データの利活用方法を検討されている方 データサイロにお悩みの方 【前提知識】 リレーショナルデータベースの基礎知識があれば望ましいです。Data Fabric Studioはローコードツールです。
記事
Mihoko Iijima · 2020年10月27日

【はじめてのInterSystems IRIS】Interoperability(相互運用性):プロダクションとは

この記事はこちらの投稿の続きの内容です。 前回の記事では、Interoperability(相互運用性)メニューを利用してシステム統合を行う際、どのような仕組みで動作しているのかについて解説しました。 今回の記事では、Interoperability(相互運用性)メニューを利用してでシステム統合を行うためにどのような開発を行うのか、について解説します。 最初に、どんな流れを作りたいのか?を考えながら、以下の内容を作成していきます。 プロダクション メッセージ コンポーネント ビジネス・サービス ビジネス・プロセス ビジネス・オペレーション プロダクション については、システム統合を行うために必要なコンポーネントの指定と、コンポーネントの設定を保存しておくため定義で、管理ポータルを使用して設定します(内部的にはプロダクション用クラス定義として保存されます)。 例えば、一定間隔で指定ディレクトリに置かれたファイルを処理するビジネス・サービスを作成している場合、「どのディレクトリを監視するのか」「どのファイルを処理したらいいのか」を具体的に設定します。この設定を保存するために用意するのが プロダクション です。 なお、設定内容はデータを送受信するコンポーネントが使用するアダプタにより異なります。 アダプタとは、外部システムとの接続を簡単にするためのクラスで、メール/ファイル/SOAP/FTP/HTTP/SQL/TCP などプロトコル別のものもあれば、HL7など規格に対応したアダプタもあります。 アダプタ詳細についてドキュメント(プロトコル別のアダプタ や EDIドキュメントに関連するアダプタ)をご参照ください。 プロダクションには必要なコンポーネントを定義しておくので、「プロダクションを開始」することでシステム統合が開始し、「プロダクションを停止」することでシステム統合が停止します。 プロダクションを完成させるために必要な開発は、システム統合に必要な部品の作成ですが、具体的には以下の内容を作成します。 メッセージ コンポーネント(ビジネス・サービス/ビジネス・プロセス/ビジネス・オペレーション) データ変換 などなど 上記内容はこの後の記事でゆっくり解説します。 まずは、サンプルのプロダクションを利用してプロダクションを開始し、設定内容を確認しながら実際にデータを流してメッセージの流れを確認してみましょう。 サンプルは https://github.com/Intersystems-jp/selflearning-interoperability からダウンロードいただけます。 コンテナを利用される場合は、git clone でサンプルコードをダウンロードいただき、clone で作成したディレクトリに移動後、docker-compose up -d を実行するだけ!とても簡単です。手順はこちらをご参照ください(コンテナ作成に少し時間がかかります)。 コンテナを利用されない場合は、サンプルダウンロード後、新規でネームスペースを作成し、作成したネームスペースに src フォルダ以下にあるクラス定義ファイル(拡張子.cls)をすべてインポートしてください。ネームスペース作成の流れなどは、こちらの記事の 07:03以降のビデオをご参照ください。 サンプルコード詳細については README もぜひご参照ください。 準備ができたら、管理ポータルにアクセスします(Webサーバのポート番号はご利用環境に合わせて変更してください)。 localhost:52773/csp/sys/UtilHome.csp 管理ポータル > [Interoperability] > [構成] > [プロダクション] にアクセスします. コンテナ以外をご利用の場合はソースコードをインポートしたネームスペースに接続してから[構成] > [プロダクション] にアクセスし [開く] ボタンをクリックし [Start] > [Production] を選択したあと [開始する] ボタンをクリックします。 ※コンテナ以外をご利用の場合は、初期設定が必要です。後述の内容を事前に設定してからこの後の内容をお試しください プロダクションの画面は「サービス」「プロセス」「オペレーション」のコンポーネントごとに [●コンポーネント名] と表示されます。 コンポーネント名をクリックすると、画面右端の「設定」タブの内容が変わります。 例えば、Start.GetKionOperation をクリック(シングルクリック)したときの表示は以下の通りです。 このコンポーネントは Web API に接続するための [HTTPサーバ] [URL] の設定があります。設定の下の方には API key を入力するための [appid] 欄があります。ここに取得した API key を入力し「適用」ボタンをクリックします。 コンテナをご利用の方はこれで設定は完了です。つづきはこちらをご覧ください。 コンテナ以外でお試しいただいてる方 事前に以下2つの設定を行ってください。 1) SSLクライアントの設定。 接続先の Web API の通信が https で行われるため、事前にIRIS側に SSLクライアントの設定を行います。サンプルプロダクションの設定に合わせるため、[openweather] の名称で作成します。プロダクション上の設定は以下の通りです。 管理ポータル > [システム管理] > [セキュリティ] > [SSL/TLS 構成] > [新規構成の作成]ボタンクリックし、「構成名」に openweather と記入したら「保存」ボタンをクリックして終了です。 2) REST用ベースURLの作成 サンプルプロダクションでは、RESTで情報入力できるように設定しています。IRIS 側の設定として REST のベースURL を設定しておく必要があります。例では、/start をベースURLに設定しています。サンプルをインポートしたネームスペースに Start.REST クラスが存在しますので、このクラスをディスパッチクラスに指定し、アクセス時の認証を省略したいので、アプリケーションロールとして %All を追加します。 管理ポータル > [システム管理] > [セキュリティ] > [アプリケーション] > [ウェブ・アプリケーションパス] > [新しいウェブ・アプリケーションを作成] ボタンをクリックします。 名前欄に /start 、ネームスペースにサンプルをインポートされたネームスぺ―スを指定、ディスパッチクラス名に Start.REST を指定、許可された認証方法に「認証なし」を選択し、一旦保存します。 保存後、「アプリケーションロール」タブで %All ロールをアプリケーションロールに追加します。 データを送信してみる 準備ができたら、REST で情報を送信できるビジネスサービスを利用して、情報を流してみます。 http://localhost:52773/start/weather/ちくわ/豊橋市 上記例は、「豊橋市」で「ちくわ」を購入した人がいた、を想定したURLです。 実行後の画面は以下の通りです。 プロダクションに流れたメッセージを確認します。 管理ポータル > [Interoperability] > [構成] > [プロダクション] の画面で、名称をクリックします。 画面右側で「メッセージ」タブを選択し、ヘッダ欄にある番号をクリックします。もし表示されていない場合はブラウザをリロードします。 トレース画面を利用すると、コンポーネント間を送受信したメッセージの情報を確認できます。水色の枠の部分では、Web API から気象情報を取得して返送していることがわかります。 このように、トレースを利用すると、その時どんな順序でどんなデータが送受信されていたのかを確認できます。 この記事を通して、プロダクションにはシステム統合に必要なコンポーネントとその設定が定義されていることを サンプルコードの設定を参照しながら確認できました。 また、トレース画面を利用することでプロダクションに流れるメッセージの詳細を時系列に参照できることも確認できました。 この後の記事では、このトレースに表示されている「メッセージ」を作成するときの考え方や、実際の定義方法を解説します。
記事
Hiroshi Sato · 2021年11月24日

InterSystems製品開始時に特定の処理を実行する方法

これは InterSystems FAQ サイトの記事です。 InterSystems製品開始時に、OSの実行ファイルやコマンド、InterSystems製品内に作成したプログラムを実行したい場合は SYSTEM^%ZSTART ルーチンに処理を記述します。 (%ZSTARTルーチンは%SYSネームスペースで作成します) SYSTEM^%ZSTART に記述する処理は、事前にあらゆる条件下でうまく動作することを確認してください。 ^%ZSTART ルーチンの記述ミスや、記述は正しくとも起動時にコマンドが応答を返さなかったり処理でエラーが起こった場合、InterSystems製品が起動できなくなることがあります。 詳しくは、以下ドキュメントをご参照ください。 %ZSTART、%ZSTOPルーチンの記述について【IRIS】%ZSTART、%ZSTOPルーチンの記述について
お知らせ
Toshihiko Minamoto · 2021年6月22日

チャットしよう! DiscordのInterSystems Developersにご参加ください!

開発者コミュニティの皆さん、こんにちは。 Discordについてはご存知の方も多いと思いますし、実際にチャットされている方も多いと思います。開発者のソーシャルクラブに参加いただき、ぜひインターシステムズの技術をより身近なものにしてください! 💥 InterSystems Developers Discord Channel 💥 超高速なコミュニケーション手段としてご利用ください。 開発者コミュニティDiscordサーバには、たくさんのインターシステムズに関連するチャンネルや会議、日常生活での会話のチャンネルがあります。開発者の世界をより身近なものにするため、ぜひクラブに参加してください! では InterSystems Developers Discord でお会いしましょう✌ 改善提案や、チャンネルの作成等、皆さんのお考えやコメントをご遠慮なくお寄せください。
お知らせ
Toshihiko Minamoto · 2022年3月4日

3/1 複数のInterSystems IRIS for Health 、HealthShare への勧告

InterSystems Product Alerts and Advisories page に投稿された14件の勧告へのリンクです。HealthShare製品は全て、IRIS for Healthは最初の3件について影響があります。 FHIR Search with an Assigning Authority but no Identifier Omits Results Some FHIR Queries that use _include and _revinclude Fail Silently FHIR Queries May Omit Results FHIR Queries that Specify a Quantity but no Units of Measure Return Incorrect Results Time Zone Offsets not Applied to FHIR Resources FHIR Searches that Specify a Time Range may Improperly Include or Exclude some Data Clinical Viewer may lose “Standard Types” on Upgrade Cross-site Scripting Issue in the Clinical Viewer MPI Demographics May Contain Unconsented Data When No Consent Group is Specified, Clinical Consent Rules May Fail HS.Stream Global in Edge Gateways Not Cleared After Errors HealthShare Care Community Tasks can be Assigned to Members of a Care Team who do not have Permission to View or Take Action on them Cross-site Scripting Vulnerability in HealthShare Patient Index Medications with a Blank “Start Date” may not Display in the Clinical Viewer
お知らせ
Toshihiko Minamoto · 2023年4月18日

InterSystems IRIS, IRIS for Health, HealthShare Health Connect 2022.1.3 リリースのお知らせ

インターシステムズは、InterSystems IRIS、InterSystems IRIS for Health、HealthShare Health Connect 2022.1.3 のメンテナンスリリースを公開しましたのでお知らせします。このリリースでは、これまでの2022.1.xリリースに対して厳選された機能とバグフィックスが提供されます。 変更点についての追加情報は、これらのページでご覧いただけます InterSystems IRIS InterSystems IRIS for Health HealthShare Health Connect より良い製品にするため、開発者コミュニティにご意見をお寄せください。 ソフトウェアの入手方法 本ソフトウェアは、古典的なインストールパッケージとコンテナイメージの両方が利用可能です。 利用可能なインストーラとコンテナ・イメージの詳細なリストについては、「サポート対象プラットホーム」を参照してください。 InterSystems IRISとInterSystems IRIS for Healthのインストール・パッケージは、このWRCのInterSystems IRIS Data Platform Full Kits のページから入手できます。 HealthShare Health Connectキットは、WRCのHealthShare Full Kitsのページから入手できます。 コンテナ・イメージは、InterSystems Container Registryから入手できます。 このリリースには、Community Editionのキットやコンテナはありません。 このリリースに含まれるすべてのキットおよびコンテナの番号は、2022.1.3.668.0 です。
記事
Mihoko Iijima · 2020年4月28日

GitLabを使用したInterSystemsソリューションの継続的デリバリー - 索引

この連載記事では、InterSystemsの技術とGitLabを使用したソフトウェア開発に向けて実現可能な複数の手法を紹介し、議論したいと思います。 次のようなトピックについて説明します。 第1回 Gitの基本、Gitの概念を高度に理解することが現代のソフトウェア開発にとって重要である理由 Gitを使用してソフトウェアを開発する方法(Gitのフロー) 第2回 GitLabワークフロー - アイデアからユーザーフィードバックまでの完全なソフトウェアライフサイクルプロセス。 継続的デリバリー - チームが短いサイクルでソフトウェアを作成し、ソフトウェアをいつでも確実にリリースできるようにするソフトウェアエンジニアリング手法です。 この手法はソフトウェアの構築、テスト、リリースをより速く、より頻繁に行うことを目指しています。 第3回 GitLabのインストールと構成 利用環境のGitLabへの接続 第4回 継続的デリバリーの構成 第5回 コンテナとその使用方法(および使用する理由)。 第6回 コンテナを使用した継続的デリバリーパイプラインの主要コンポーネント それらすべての連携方法。 第7回 コンテナを使用した継続的デリバリーの構成 第8回 InterSystems Cloud Managerを使用した継続的デリバリーの構成 第9回 コンテナアーキテクチャ この連載記事では、継続的デリバリーの一般的な手法について説明しました。 これは非常に広範なトピックであり、この連載記事の内容は完成されたものではなく、レシピを集めたものとして考えてください。 アプリケーションのビルド、テスト、配信を自動化したい場合、一般的には継続的デリバリー、特にGitLabが最適です。 継続的デリバリーとコンテナを使えば、必要に応じてワークフローをカスタマイズできます。
お知らせ
Masahito Miura · 2023年7月13日

InterSystems IRIS, IRIS for Health, HealthShare Health Connect 2023.1.1 リリースのお知らせ

インターシステムズは、InterSystems IRIS データ・プラットフォーム、InterSystems IRIS for Health 、HealthShare Health Connect のメンテナンスバージョン 2023.1.1 をリリースしました。これらのリリースは、2023.1.0 に対するバグフィックスを提供します。詳細な変更リスト/アップグレード・チェックリストは、以下のページをご参照ください。(すべて英語版です) ・InterSystems IRIS・InterSystems IRIS for Health・HealthShare Health Connectソフトウェアの入手方法 本製品は、従来からのインストーラパッケージ形式と、コンテナイメージ形式をご用意しています。利用可能なインストーラとコンテナイメージの一覧は、サポートプラットフォームページ(英語) をご覧ください。 InterSystems IRIS と IRIS for Health のインストーラパッケージは WRC Direct の IRIS データプラットフォームダウンロードページ から入手できます。 HealthShare Health Connect のインストーラパッケージは WRC Direct の HealthShareダウンロードページ から入手できます。コンテナイメージは InterSystems Container Registry から入手できます。 各製品のバージョン番号は 2023.1.1.380.0 です。
記事
Tomohiro Iwamoto · 2020年11月23日

InterSystemsIRIS 用 ODataAPI アダプタの作成

この記事では、OData API 標準に基づいて開発された RESTful API サービスを利用するための IRIS クライアントの開発について説明します。 HTTP リクエストを作成し、JSON ペイロードの読み取りと書き込みを行い、それらを組み合わせて OData 用の汎用クライアントアダプタを構築する方法を確認するため、多数の組み込み IRIS ライブラリを説明します。 また、JSON を永続オブジェクトに逆シリアル化するための新しい JSON アダプタについても説明します。 RESTful API の操作 REST は World Wide Web の標準化に関する作業から作成された一連の設計原則です。 これらの原則はあらゆるクライアントサーバー通信に適用でき、HTTP API が RESTful であることを説明するためによく使用されます。 REST はステートレスなリクエスト、キャッシュ処理、統一した API 設計など、さまざまな原則を網羅しています。 ただし、詳細な実装については網羅していません。また、これらのギャップを埋めるための一般的な API 仕様は存在しません。 この曖昧さは、RESTful API に幾分かの理解、ツール、より厳密なエコシステムを中心によく構築されるライブラリが不足している原因となっています。 特に、開発者は RESTful API の検出と文書化のために独自のソリューションを構築する必要があります。 OData OData は、一貫性のある RESTful API を構築するための OASIS の仕様です。 OASIS コミュニティには、Microsoft / Citrix / IBM / Red Hat / SAP などの有名なソフトウェア会社が参加しています。 2007 年に OData 1.0 が最初に導入され、最新バージョンの 4.1 が今年リリースされました。 OData の仕様は、メタデータ、一貫性のある操作の実装、クエリ、例外処理などを対象としています。 また、アクションや関数などの追加機能も対象としています。 TripPinWS OData API の説明 この記事では、Odata.org が例示している TripPinWS API を使用します。 他の RESTful API と同様に、一般的にはサービスのベース URL が必要です。 OData でこのベース URL にアクセスすると、API エンティティのリストも返されます。 この API には Photos、People、Airlines、Airports、Me のエンティティと、GetNearestAirport という関数が含まれていることがわかります。 応答には、TripPinWS メタデータドキュメントへのリンクも含まれています。 [https://services.odata.org/V4/(S(djd3m5kuh00oyluof2chahw0))/TripPinServiceRW/$metadata](https://services.odata.org/V4/(S(djd3m5kuh00oyluof2chahw0))/TripPinServiceRW/%24metadata) このメタデータは XML ドキュメントとして実装されており、独自の XSD ドキュメントが含まれています。 これにより、IRIS XML スキーマウィザードから生成されるコードを使用してメタデータドキュメントを消費する可能性が広がります。 メタデータドキュメントは一見かなり複雑に見えるかもしれませんが、エンティティのスキーマ定義を構成するために使用されるタイプのプロパティを表しているだけです。 次の URL を使用すると、API から People のリストを取得できます。 この URL は 8 人のリストを返します。この 8 という数値は、厳密な結果ごとのエンティティ数の上限値です。 実際には、これよりもはるかに大きな上限値を使用することになるでしょう。 ただし、OData が @odata.nextLink などの追加のハイパーテキストリンクを含んでいる例も示されています。そのリンクを使用すると、People を検索した結果の次のページを取得できます。 また、次のようにして上位 1 件の結果のみを選択するなど、クエリ文字列値を使用して結果リストを絞り込むこともできます。 [https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?$top=1](https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?%24top=1) FirstName でリクエストを絞り込むこともできます。 [https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?$filter=FirstName eq 'Russell'](https://services.odata.org/V4/(S(4hkhufsw5kohujphemn45ahu))/TripPinServiceRW/People?%24filter=FirstName%20eq%20'Russell') この例では eq 演算子を使用し、「Russell」に等しいすべての FirstName を抽出しました。 ここでは対象の文字列を一重引用符で囲むことが重要です。 OData では、さまざまな演算子を組み合わせて表現力の高い検索クエリを作成することができます。 IRIS %Net パッケージ IRIS には、包括的な標準ライブラリが含まれています。 私たちは FTP / メール / LDAP / HTTP などのプロトコルをサポートする %Net パッケージを使用することになります。 TripPinWS サービスを使用するには、HTTPS を使用する必要があります。そのためには、IRIS 管理ポータルに HTTPS 構成を登録する必要があります。 複雑な証明書をインストールする必要はないため、次のように数ステップで作業を完了することができます。 * IRIS 管理ポータルを開きます。 * [ システム 管理] > [セキュリティ] > [SSL/TLS 構成] をクリックします。 * [新規構成の作成] ボタンをクリックします。 * 「odata_org」という構成名を入力し、[保存] をクリックします。 * ここでは任意の名前を選択できますが、記事の残りの部分では odata_org を使用します。 これで、HttpRequest クラスを使用して全員のリストを取得できるようになりました。 Get() が動作すると、OK の場合に 1 が返ってきます。 その後、次のように応答オブジェクトにアクセスして結果を端末に出力できます。 DC>set req=##class(%Net.HttpRequest).%New()DC>set req.SSLConfiguration="odata_org"DC>set sc=req.Get("https://services.odata.org:443/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People")DC>w sc1DC>do req.HttpResponse.OutputToDevice() 先に進む前に、基本的な HttpRequest を自由に試してみてください。 Airlines や Airports を取得してみたり、不正な URL を入力した場合にどのようなエラーが返ってくるかを調べたりしてください。 汎用 OData クライアントの開発 HttpRequest クラスを抽象化し、さまざまな OData クエリオプションの実装を簡単にする汎用 OData クライアントを作成しましょう。 このクライアントを DcLib.OData.Client と名付け、%RegisteredObject を拡張して作成します。 また、特定の OData サービスの名前を定義するために使用できるいくつかのサブクラスと、HttpRequest オブジェクトなどのランタイムオブジェクトと値をカプセル化するいくつかのプロパティを定義します。 OData クライアントのインスタンス化を簡単にするため、%OnNew() メソッド(クラスのコンストラクタメソッド)もオーバーライドし、それを使用して実行時のプロパティを設定します。 Class DcLib.OData.Client Extends %RegisteredObject{Parameter BaseURL;Parameter SSLConfiguration;Parameter EntityName;Property HttpRequest As %Net.HttpRequest;Property BaseURL As %String;Property EntityName As %String;Property Debug As %Boolean [ InitialExpression = 0 ];Method %OnNew(pBaseURL As %String = "", pSSLConfiguration As %String = "") As %Status [ Private, ServerOnly = 1 ]{   set ..HttpRequest=##class(%Net.HttpRequest).%New()   set ..BaseURL=$select(pBaseURL'="":pBaseURL,1:..#BaseURL)   set ..EntityName=..#EntityName   set sslConfiguration=$select(pSSLConfiguration'="":pSSLConfiguration,1:..#SSLConfiguration)   if sslConfiguration'="" set ..HttpRequest.SSLConfiguration=sslConfiguration   quit $$$OK}} このように DcLib.OData.Client を拡張し、BaseURL と SSL 構成パラメーターを一箇所で設定することにより、TripPinWS サービスに固有のクライアントクラスを定義できるようになります。 Class TripPinWS.Client Extends DcLib.OData.Client{Parameter BaseURL = "https://services.odata.org:443/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW";Parameter SSLConfiguration = "odata_org";} この基本クライアントを使用すれば、サービスで使用したいエンティティタイプごとにクラスを作成できます。 この新しいクライアントクラスを拡張すれば、EntityName パラメーターでエンティティ名を定義するだけで済みます。 Class TripPinWS.People Extends TripPinWS.Client{Parameter EntityName = "People";} 次に、エンティティのクエリを簡単にするため、基本の DcLib.OData.Client クラスにさらにいくつかのメソッドを追加する必要があります。 Method Select(pSelect As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$select",pSelect)   return $this}Method Filter(pFilter As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$filter",pFilter)   return $this}Method Search(pSearch As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$search",pSearch)   return $this}Method OrderBy(pOrderBy As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$orderby",pOrderBy)   return $this}Method Top(pTop As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$top",pTop)   return $this}Method Skip(pSkip As %String) As DcLib.OData.Client{   do ..HttpRequest.SetParam("$skip",pSkip)   return $this}Method Fetch(pEntityId As %String = "") As DcLib.OData.ClientResponse{   if pEntityId="" return ##class(DcLib.OData.ClientResponse).%New($$$ERROR($$$GeneralError,"Entity ID must be provided"),"")   set pEntityId="('"_pEntityId_"')"   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"   set sc=..HttpRequest.Get(..BaseURL_..EntityName_pEntityId,..Debug)   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"one")   quit response}Method FetchCount() As DcLib.OData.ClientResponse{   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"   set sc=..HttpRequest.Get(..BaseURL_..EntityName_"/$count")   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"count")   quit response}Method FetchAll() As DcLib.OData.ClientResponse{   #dim response As DcLib.OData.ClientResponse   if $extract(..BaseURL,*)'="/" set ..BaseURL=..BaseURL_"/"   set sc=..HttpRequest.Get(..BaseURL_..EntityName,..Debug)   set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse,"many")   if response.IsError() return response   //応答に nextLink が含まれる場合は、さらにデータを取得し続ける必要があります。   while response.Payload.%IsDefined("@odata.nextLink") {       //前の値を配列に退避し、新しい値をその配列にプッシュしてから       //新しい応答にそれを設定し直し、新しい値のイテレータを作成します。       set previousValueArray=response.Payload.value       set sc=..HttpRequest.Get(response.Payload."@odata.nextLink",..Debug)       set response=##class(DcLib.OData.ClientResponse).%New(sc,..HttpRequest.HttpResponse)       if response.IsError() return response       while response.Value.%GetNext(.key,.value) {           do previousValueArray.%Push(value)           }       set response.Payload.value=previousValueArray       set response.Value=response.Payload.value.%GetIterator()   }   return response} ここでは 9 つの新しいメソッドを追加しました。 最初の 6 つはクエリオプションを定義するためのインスタンスメソッドであり、最後の 3 つは 1 つのエンティティ、すべてのエンティティ、またはすべてのエンティティのカウントを取得するためのメソッドです。 最初の 6 つのメソッドは、基本的に HTTP リクエストオブジェクトにパラメーターを設定するためのラッパーです。 コードを実装しやすくするため、これらの各メソッドはこのオブジェクトのインスタンスを返し、メソッドをチェーン化できるようにしています。 メインの Fetch() メソッドについて説明する前に、Filter() メソッドの動作を見てみましょう。 set people=##class(TripPinWS.People).%New().Filter("UserName eq 'ronaldmundy'").FetchAll()while people.Value.%GetNext(.key,.person) { write !,person.FirstName," ",person.LastName    } このメソッドを使用すると、次のような結果が返ってきます。 Ronald Mundy このサンプルコードでは、TripPinWS Peopleオブジェクトのインスタンスを作成しています。 これにより、ベース URL と基本クラスの証明書の構成が設定されます。 その後、その Filter メソッドを呼び出してフィルタークエリを定義してから FetchAll() を呼び出すと HTTP リクエストを呼び出すことができます。 ここで直接アクセスできるのは生の JSON データではなく、動的オブジェクトとしての People の結果であることに注意してください。 これは、例外処理を簡単にする ClientResponse オブジェクトも実装するためです。 また、返される結果のタイプに応じて動的オブジェクトを生成します。 まず、FetchAll() メソッドについて説明します。 この段階で、実装クラスが基本クラス構成で OData URL を定義しており、ヘルパーメソッドが追加のパラメーターを設定しています。FetchAll() メソッドは URL を組み立てて GET リクエストを発行する必要があります。 元のコマンドラインの例と同様に HttpRequest クラスで Get() メソッドを呼び出し、その結果から ClientResponse を作成します。 API が一度に 8 件しか結果を返さないため、このメソッドは複雑になっています。 コード内でこの制限に対処し、前の結果の nextLink 値を使用して最後のページに到達するまで次の結果ページを取得し続ける必要があります。 追加の各ページを取得する際に前の結果配列を保管してから、新しい結果をそれぞれその配列にプッシュしています。 Fetch() / FetchAll() / FetchCount() メソッドは、DcLib.OData.ClientResponse というクラスのインスタンスを返します。 例外を処理し、有効な JSON 応答を自動的に逆シリアル化するためにこのクラスを作成しましょう。 Class DcLib.OData.ClientResponse Extends %RegisteredObject{Property InternalStatus As %Status [ Private ];Property HttpResponse As %Net.HttpResponse;Property Payload As %Library.DynamicObject;Property Value;Method %OnNew(pRequestStatus As %Status, pHttpResponse As %Net.HttpResponse, pValueMode As %String = "") As %Status [ Private, ServerOnly = 1 ]{   //直接の HTTP エラーをチェック   set ..InternalStatus = pRequestStatus   set ..HttpResponse = pHttpResponse   if $$$ISERR(pRequestStatus) {       if $SYSTEM.Status.GetOneErrorText(pRequestStatus)["" set ..InternalStatus=$$$ERROR($$$GeneralError,"Could not get a response from HTTP server, server could be uncontactable or server details are incorrect")       return $$$OK   }      //モードが count の場合、応答は JSON ではなく単なる数値になります。   //数値であることを確認し、true ならばすべて ok を返しますが、それ以外の場合は   //JSON で表現されるエラーを検出するためにフォールスルーします。   if pValueMode="count" {       set value=pHttpResponse.Data.Read(32000)       if value?1.N {           set ..Value=value           return $$$OK       }   }      //JSON ペイロードをシリアル化し、シリアル化エラーをキャッチします。   try {       set ..Payload={}.%FromJSON(pHttpResponse.Data)       } catch err {       //先に HTTP ステータスコードのエラーをチェックします。       if $e(pHttpResponse.StatusCode,1)'="2" {           set ..InternalStatus = $$$ERROR($$$GeneralError,"Unexpected HTTP Status Code "_pHttpResponse.StatusCode)           if pHttpResponse.Data.Size>0 return $$$OK       }       set ..InternalStatus=err.AsStatus()       return $$$OK   }      //OData エラーのペイロードをチェックします。   if ..Payload.%IsDefined("error") {       do ..HttpResponse.Data.Rewind()       set error=..HttpResponse.Data.Read(32000)       set ..InternalStatus=$$$ERROR($$$GeneralError,..Payload.error.message)           return $$$OK   }      //すべて ok なら、必要なモード(many, one, count)に一致するように応答値を設定します。   if pValueMode="one" {       set ..Value=..Payload   } else {       set iterator=..Payload.value.%GetIterator()       set ..Value=iterator   }      return $$$OK}Method IsOK(){   return $$$ISOK(..InternalStatus)}Method IsError(){   return $$$ISERR(..InternalStatus)}Method GetStatus(){   return ..InternalStatus}Method GetStatusText(){   return $SYSTEM.Status.GetOneStatusText(..InternalStatus)}Method ThrowException(){   Throw ##class(%Exception.General).%New("OData Fetch Exception","999",,$SYSTEM.Status.GetOneStatusText(..InternalStatus))}Method OutputToDevice(){   do ..HttpResponse.OutputToDevice()}} ClientResponse オブジェクトのインスタンスが与えられた場合、最初にテストを実行してエラーがあったかどうかを確認することができます。 エラーは複数のレベルで発生する可能性があるため、単一の使いやすいソリューションでエラーを返すのが望ましいです。 set response=##class(TripPinWS.People).%New().Filter("UserName eq 'ronaldmundy'").FetchAll()if response.IsError() write !,response.GetStatusText() quit IsOK() メソッドと IsError() メソッドはオブジェクトのエラーをチェックします。 エラーが発生した場合は GetStatus() または GetStatusText() を呼び出してエラーにアクセスするか、ThrowException() を使用してエラーを例外ハンドラに渡すことができます。 エラーが発生していない場合、ClientResponse は生のペイロードオブジェクトを応答ペイロードのプロパティに代入します。 set ..Payload={}.%FromJSON(pHttpResponse.Data) 次に、応答の Value プロパティを単一のインスタンスとして、または多数の結果を探索するための配列イテレータとして、ペイロード内のメインデータ配列に設定します。 私はこれらすべてのコードを GitHub  上の単一のプロジェクトに格納しています。そこで全体を見直せば、より深く理解することができるでしょう。 次の例はすべて、ソースの GitHub プロジェクトに含まれています。 OData クライアントの使用 基本 Client クラスに関しては、With() メソッドも理解しておく必要があります。 すべてのエンティティのインスタンスを作成する代わりに、単一のクライアントクラスだけで With() メソッドを使用することができます。 With() メソッドは、指定されたエンティティ名で新しいクライアントを定義します。 ClassMethod With(pEntityName As %String) As DcLib.OData.Client{   set client=..%New()   set client.EntityName=pEntityName   return client} このメソッドを使用すれば、次のように基本 Client クラスですべての People を取得できます。 /// 基本クライアントクラスと .With("People") を使用してすべての "People" を取得しますClassMethod TestGenericFetchAllUsingWithPeople(){   #dim response As DcLib.OData.ClientResponse   set response=##class(TripPinWS.Client).With("People").FetchAll()      if response.IsError() write !,response.GetStatusText() quit      while response.Value.%GetNext(.key,.person) {       write !,person.FirstName," ",person.LastName       }} または、次のようにクラスごとのエンティティを使用します。 /// People クラスを使用してすべての "People" を取得しますClassMethod TestFetchAllPeople(){   #dim people As DcLib.OData.ClientResponse   set people=##class(TripPinWS.People).%New().FetchAll()      if people.IsError() write !,people.GetStatusText() quit      while people.Value.%GetNext(.key,.person) {       write !,person.FirstName," ",person.LastName       }} ご覧のとおり、これらの方法は非常に似通っています。 どちらの方法を選択すべきかは、具体的なエンティティについて自動補完がどれほど重要であるか、および具体的なエンティティクラスにエンティティ固有のメソッドを追加するかどうかによって異なります。 DC>do ##class(TripPinWS.Tests).TestFetchAllPeople()Russell WhyteScott KetchumRonald Mundy… およびその他の人 次に、Airlines についても同じ処理を実装しましょう。 /// すべての "Airlines" を取得しますClassMethod TestFetchAllAirlines(){   #dim airlines As DcLib.OData.ClientResponse   set airlines=##class(TripPinWS.Airlines).%New().FetchAll()      if airlines.IsError() write !,airlines.GetStatusText() quit      while airlines.Value.%GetNext(.key,.airline) {       write !,airline.AirlineCode," ",airline.Name       }} そして、コマンドラインから次の結果を得ることができます。 DC>do ##class(TripPinWS.Tests).TestFetchAllAirlines()AA American AirlinesFM Shanghai Airline… およびその他の航空会社 次は Airports の実装です。 /// すべての "Airports" を取得しますClassMethod TestFetchAllAirports(){   #dim airports As DcLib.OData.ClientResponse   set airports=##class(TripPinWS.Airports).%New().FetchAll()      if airports.IsError() write !,airports.GetStatusText() quit      while airports.Value.%GetNext(.key,.airport) {       write !,airport.IataCode," ",airport.Name       }} そして、コマンドラインから次の結果を得ることができます。 DC>do ##class(TripPinWS.Tests).TestFetchAllAirports()SFO San Francisco International AirportLAX Los Angeles International AirportSHA Shanghai Hongqiao International Airport… およびその他の空港 これまでは FetchAll() メソッドを使用してきました。 次のように Fetch() メソッドを使用し、エンティティの主キーを使用して単一のエンティティを取得することもできます。 /// 人の識別子を使用して単一の "People" エンティティを取得しますClassMethod TestFetchPersonWithID(){   #dim response As DcLib.OData.ClientResponse   set response=##class(TripPinWS.People).%New().Fetch("russellwhyte")      if response.IsError() write !,response.GetStatusText() quit      //新しいフォーマッターを使用して出力を美しく整形してみましょう(最新バージョンの IRIS のみ)   set jsonFormatter = ##class(%JSON.Formatter).%New()   do jsonFormatter.Format(response.Value)} この例では動的配列またはオブジェクトを取得し、整形したJSONに出力できる新しい JSON フォーマッタークラスを使用しています。 DC>do ##class(TripPinWS.Tests).TestFetchPersonWithID(){ "@odata.context":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/$metadata#People/$entity", "@odata.id":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People('russellwhyte')", "@odata.etag":"W/\"08D720E1BB3333CF\"", "@odata.editLink":"http://services.odata.org/V4/(S(jndgbgy2tbu1vjtzyoei2w3e))/TripPinServiceRW/People('russellwhyte')", "UserName":"russellwhyte", "FirstName":"Russell", "LastName":"Whyte", "Emails":[   "Russell@example.com",   "Russell@contoso.com" ], "AddressInfo":[   {     "Address":"187 Suffolk Ln.",     "City":{       "CountryRegion":"United States",       "Name":"Boise",       "Region":"ID"     }   } ], "Gender":"Male", "Concurrency":637014026176639951} OData の永続化 最後のいくつかの例では、新しい JSON アダプタクラスを使用して OData JSON を永続オブジェクトに逆シリアル化する方法を説明します。 ここでは Person、Address、City の 3 つのクラスを作成しますが、いずれも Person のデータ構造を OData メタデータに反映します。 また、@odata.context のような追加の OData プロパティが逆シリアル化エラーをスローしないよう、1 に設定された %JSONIGNOREINVALIDFIELD を使用します。 Class TripPinWS.Model.Person Extends (%Persistent, %JSON.Adaptor){Parameter %JSONIGNOREINVALIDFIELD = 1;Property UserName As %String;Property FirstName As %String;Property LastName As %String;Property Emails As list Of %String;Property Gender As %String;Property Concurrency As %Integer;Relationship AddressInfo As Address [ Cardinality = many, Inverse = Person ];Index UserNameIndex On UserName [ IdKey, PrimaryKey, Unique ];}   Class TripPinWS.Model.Address Extends (%Persistent, %JSON.Adaptor){Property Address As %String;Property City As TripPinWS.Model.City;Relationship Person As Person [ Cardinality = one, Inverse = AddressInfo ];}   Class TripPinWS.Model.City Extends (%Persistent, %JSON.Adaptor){Property CountryRegion As %String;Property Name As %String;Property Region As %String;} 次に、OData サービスから Russel Whyte を取得し、Person モデルの新しいインスタンスを作成した後に応答値を使用して %JSONImport() メソッドを呼び出します。 これにより、Address と City の詳細とともに Person オブジェクトにデータが入力されます。 ClassMethod TestPersonModel(){   #dim response As DcLib.OData.ClientResponse   set response=##class(TripPinWS.People).%New().Fetch("russellwhyte")      if response.IsError() write !,response.GetStatusText() quit      set person=##class(TripPinWS.Model.Person).%New()      set sc=person.%JSONImport(response.Value)   if $$$ISERR(sc) write !!,$SYSTEM.Status.GetOneErrorText(sc) return      set sc=person.%Save()   if $$$ISERR(sc) write !!,$SYSTEM.Status.GetOneErrorText(sc) return} 次に、次のように SQL コマンドを実行してデータが永続化されていることを確認できます。 SELECT ID, Concurrency, Emails, FirstName, Gender, LastName, UserNameFROM TripPinWS_Model.PersonID                          Concurrency                      Emails                                                                                    FirstName    Gender    LastName    UserNamerussellwhyte    637012191599722031    Russell@example.com Russell@contoso.com    Russell            Male         Whyte            russellwhyte 最終的な考え 上記のように、組み込みの %NET クラスを使用して RESTful な OData サービスを利用するのは簡単です。 少しばかりの追加ヘルパーコードを使用すれば、OData クエリの構築を単純化し、エラーレポートを統合し、JSON を動的オブジェクトに自動的に逆シリアル化できます。 そして、必要に応じてベース URL と HTTPS 構成を指定するだけで、新しい OData クライアントを作成できます。 さらに、この単一のクラスと .With('エンティティ') メソッドを使用してサービス上の任意のエンティティを利用するか、関心のあるエンティティの名前付きサブクラスを作成することができます。 また、新しい JSON アダプタを使用して JSON 応答を永続クラスに直接逆シリアル化できることも説明しました。 現実的には最初にこのデータを非正規化することを検討し、JSON アダプタクラスがカスタムマッピングで機能することを確認する必要があります。 最後になりますが、OData の操作は非常に簡単です。 私が特注の実装でよく経験する場合よりもはるかに少ないコード量でサービス実装の一貫性を維持することができました。 私は RESTful 設計の自由さを楽しんでいますが、次のサーバーサイドソリューションでは標準を実装することを検討したいと思います。
記事
Tomoko Furuzono · 2021年6月15日

マシン名(ホスト名)を変更後OSを再起動すると、InterSystems IRIS が開始しなくなる

これは、InterSystems FAQサイトの記事です。マシン名の変更後に InterSystems IRIS(以降IRIS) を停止せずにOSを再起動すると、IRISが開始できないという問題が発生します。 開始するためには、<インストールディレクトリ>\mgr\iris.ids ファイルを削除してください。 iris.ids には、起動したノード名や共有メモリの情報(共有メモリID)が格納されていて、IRIS 開始時に作成され、停止時(iris stop または iris force を実施した場合)に削除されます。 IRIS を停止せずにOSを停止(再起動)した場合、IRISの開始情報が含まれる iris.ids が残ったままとなることがあります。IRIS は次の開始作業で iris.ids を確認します。マシン名が変更されていると起動しているノード名の情報が iris.ids の内容と異なる為、IRIS は正常に起動しません。 マシン名を変更後にOSを停止(再起動)する際には、必ず事前に IRIS を停止するようにしてください。 ※予期せぬ問題を防ぐためにも、OS停止時には事前に IRIS を停止することが理想的です。※InterSystemsの他製品についても同様です。詳しくは関連トピックをご参照ください。