データへのアクセスをフィルタリングすることは、アプリケーションへの悪意あるアクセスを防止するために必須の機能です。
これまでは、データクラス全体またはその属性の一部を RESTリソースとして公開したり公開しなかったりすることができました。これもデータアクセスを制限する便利な手段でしたが、4D v19 R8 では、不正なユーザーからデータを保護するための、強力で完全にカスタマイズ可能なシステムを提供できることを嬉しく思います。データを保護するシステムは、誰が・どのデータにアクセスするかに基づいて機能します。
データへのアクセスはいくつかのレベル (最も広範囲なものから最も局所的なものまで) で調整できるようになり、異なるビジネスに取り組む多くのユーザーに対応が可能になりました。
スケーラブルなWebセッションに続くこの機能により、信頼性の高い保護されたアプリケーションを構築できます。
使用コンテキスト
この新機能は、ORDAの概念とWebセッション (より正確にはスケーラブルWebセッション) に基づいており、すべてのWebプロセス が対象となります (RESTリクエスト や、リモートデータストアで受信したリクエストのほか、4DACTION や 4Dタグなどの Webプロセスも) 。
主なコンセプト
アプリケーションで、アクセス権限を詳細に定義できるようになりました。
リソースと、このリソースに対するアクションが許可される権限を設定することができ、この設定をパーミッションと呼びます。
リソース として、データクラス属性、ORDAデータモデル関数、データクラス、データストア全体 が指定できます。
利用可能なアクションは、create (作成)、read (読み取り)、update (更新)、drop (削除)、execute (関数の実行)、promote (関数の昇格)、および describe (記述) です。
データクラス属性に対するパーミッションの例
Recordsデータクラスの personalNotes 属性は、medicalAction権限で読み取れます。
er-style:solid;border-color:1px。
リソース Records.personalNotes | 権限名 |
---|---|
read (読み取り) | medicalAction |
データクラス関数に対するパーミッションの例
Recordsデータクラスに定義された関数 deleteOldRecords() はadministrate権限で実行できます。
リソース Records.deleteOldRecords() | 権限名 |
---|---|
execute (関数の実行) | administrate |
データクラスに対するパーミッションの例
Recordsデータクラスは、medicalAction権限と administrate権限で読み取れるほか、medicalAction権限でエンティティを新規作成できます。
リソース Records | 権限名 |
---|---|
read (読み取り) | medicalAction, administrate |
create (作成) |
medicalAction |
データストア全体に対するパーミッションの例
administrate権限があれば、データストアのどのデータクラスでもエンティティを削除できます。
リソース ds | 権限名 |
---|---|
drop (削除) | administrate |
アクセス権を管理するためのカスタマイズされたルールを設定するには、アプリケーションの Project/Sourcesフォルダーに roles.json ファイル を作成するだけです。
アプリケーションの各ユーザーに対して、いくつかのロール (権限のセット) を定義して関連付けることもできます。ご存じのように、スケーラブルWebセッションには、いくつかの権限を関連付けることが可能です。そして、システムがリクエストを受け取ると、データアクセスに関する制御がおこなわれ、Webセッションに保存されているユーザーの権限と、roles.json ファイルに定義されたパーミッション (要求されたアクションが権限に対し許可されているか) の確認がなされます。
リソースに対するアクションが許可されていない場合は、権限エラーが発生します。
仕組みの詳細
Webセッションに権限が設定されていない場合、それはゲストセッションとなります。デフォルトでは、このゲスト権限ですべてのデータにアクセス可能です。また、roles.jsonファイルにパーミッションを設定していない場合にも、すべてのデータが誰にでもアクセス可能です。
roles.jsonファイルでパーミッションを設定すると、データへのアクセスが制限されるようになります。
リソースに対するパーミッションは、いくつかのレベルで設定できます。それらのリソースをレベル別に以下に示します (最も広範囲なものから最も局所的なものまで)。
– データストア
– データクラス
– データクラス属性 / データクラス関数
あるレベルで設定されたパーミッションは、より詳細なレベルで指定されたパーミッションによってオーバーライドされるか、または補完されます。
下図は、あるリソースに対するアクションに関連する権限をより視覚的に表現したものです。
ゲスト権限は、なんのパーミッションも設定されていないリソースに対してアクションを実行することができます。権限をリソース/アクションに関連付ける、つまりパーミッションを設定すると同時に、データへのアクセスを制限することになります。
Tip: デフォルトでデータアクセスを拒否したい場合は、データストアのすべてのアクションに対して nobody権限を関連付け、この権限が決してセッションに渡されないようにします。
例題
シンプルなデータモデルを使って見ていきましょう:
このアプリケーションでは、次のルールを実装します:
- データストア全体では、administrate権限のみがエンティティを削除・作成できる。
- Patientsデータクラスは、medicalAction権限でのみ読み取ることができる。
- Records.personalNotes属性は、medicalAction権限でのみ読み取ることができる (たとえRecordsデータクラスが他の権限で読み取り可能であっても)。
- Recordsデータクラスで定義された関数 deleteOldRecords() は administrate権限でのみ実行できる。
- 誰にでも実行可能な authenticate()関数を使用する (guest権限)。
データストア全体では、administrate権限のみがエンティティを削除・作成できる
以下の roles.jsonファイルでは、administrate権限を定義しています。この権限には、以下のアクションを許可します:
- drop (削除)
- create (作成)
これらのアクションは、datastore (すべてのデータクラス) の全エンティティに対して許可されます。
この administrate権限のみが、これらのアクションを実行する権限を持ちます。なお、デフォルトでは、すべてのデータに対してすべてのアクションが許可されており、データストアの readアクションにはまだ何の権限も関連付けられていないため、誰でもデータストアのすべてのデータクラスを読み込むことができます。
{"privileges": [{"privilege": "administrate"}], "roles": [{}], "permissions": { "allowed": [{"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"]}] } }
Patientsデータクラスは、medicalAction権限でのみ読み取ることができる
以下の roles.jsonファイルでは、medicalAction権限を追加しています。
この medicalAction権限のみが、Patients データクラスを読み取る権限を持ちます。この権限はデータクラスレベルで設定されているため、データストアレベルで設定されている権限およびゲスト権限よりも優先されます。したがって、Patients データクラスを読み取ることができるのは、medicalAction特権のみです。なお、その他のデータクラスは、引き続き誰でも読み取ることができます。
{"privileges": ["privilege": "administrate"},{"privilege": "medicalAction"}], "roles": [{}], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"]}, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"]} ] } }
Records.personalNotes属性は、medicalAction権限でのみ読み取ることができる
この例では、readRecords権限を追加しています。readRecords権限は medicalAction権限に含まれているため、medicalAction権限は readRecords権限に許可されているすべてのアクションを実行できます。
readRecords権限と medicalAction権限は両方とも Recordsデータクラスを読み取れるものの、personalNotes 属性の読み取りは medicalAction権限に限定されます。
{"privileges": [{"privilege": "administrate"},{"privilege": "readRecords"},{"privilege": "medicalAction","includes": ["readRecords"]}], "roles": [{}], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"]}, {"applyTo": "Patients","type": dataclass","read": ["medicalAction"]}, {"applyTo": "Records","type": "dataclass","read": ["readRecords"]}, {"applyTo": "Records.personalNotes","type": "attribute","read": ["medicalAction"]} ] } }
Recordsデータクラスで定義された関数 deleteOldRecords() は administrate権限でのみ実行できる
次の例では、システムの管理者のみが古いレコードを削除できるようにする必要があるため、Recordsデータクラスに定義されている関数 deleteOldRecords() を実行する権限を administrate権限に追加しています。
これで、この関数を実行できるのは、この権限だけになります。
また、レコードを削除するにはエンティティを読み取る必要があるため、Recordsデータクラスの読み取り許可も administrate権限に追加します。
{"privileges": [{"privilege": "administrate"},{"privilege": "readRecords"},{"privilege": "medicalAction","includes": ["readRecords"]}], "roles":[{}], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"]}, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"]}, {"applyTo": "Records","type": "dataclass","read": ["readRecords","administrate"]}, {"applyTo": "Records.personalNotes","type": "attribute","read": ["medicalAction"]}, {"applyTo": "Records.deleteOldRecords","type": "method","execute": ["administrate"]} ] } }
guestでも実行可能なauthenticate関数を使用する
外部のユーザーがアプリケーションの関数を実行できないように、データストアレベルで関数の実行を none権限に制限します (Webセッションには決して渡しません)。
このシステムでは、ユーザーがアプリケーションに接続するために実行する authenticate() 関数 (DataStoreクラスに定義) を用意しています。この authenticate() 関数を実行するパーミッションを追加します。
この関数は誰にでも実行できる必要があるため、guest権限に対して executeアクションを許可します。また、Usersデータクラスがアプリケーションの外部の人間によって読み取られないように hr権限を追加します。
authenticate() 関数は、ユーザーとパスワードを確認するために Usersデータクラスを読み取る必要があります。そのため、関数を hr権限に昇格 (promote) します。Promoteアクションは、Webセッションに権限を追加します (関数の実行中のみ)。
{"privileges": [{"privilege": "administrate"},{"privilege": "readRecords"},{"privilege": "medicalAction","includes": ["readRecords"]},{"privilege": "hr"},{"privilege": "none"}], "roles": [{}], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": [adminrate"]、"create": [adminrate"]、"execute": ["none"]}, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"]}, {"applyTo": "Users","type": "dataclass","read": ["hr"]}, {"applyTo": "Records","type": "dataclass","read": ["readRecords","adminrate"]}, {"applyTo": "Records.personalNotes","type": "attribute","read": ["medicalAction"]}, {"applyTo": "Records.deleteOldRecords","type": "method","execute": ["administrate"]}, {"applyTo": "ds.authenticate","type": "method","promote": ["hr"],"execute": ["guest"]} ] } }
認証の段階
roles.jsonファイルでは、ロール (権限のセット) を定義できます。次の例では、createPatient と readRecords権限を持つ The Secretary というロールを追加しています。
このロールに関連付けられたユーザーは、createPatient および readRecords権限で許可されているすべてのアクション (患者の新規作成と記録の読み取り) を実行することができます。
{"privileges": [{"privilege": "administrate"},{"privilege": "readRecords"},{"privilege": "medicalAction","includes": ["readRecords"]},{"privilege": "hr"},{"privilege": "none"}, {"privilege":"createPatient" }], "roles": [{"role": "The Secretary","privileges":["createPatient","readRecords"]}], "permissions": { "allowed": [ {"applyTo": "ds","type": "datastore","drop": ["administrate"],"create": ["administrate"],"execute": ["none"]}, {"applyTo": "Patients","type": "dataclass","read": ["medicalAction"],"create": ["createPatient"]}, {"applyTo": "Users","type": "dataclass","read": ["hr"]}, {"applyTo": "Records",type": "dataclass","read": ["readRecords","administrate"]}, {"applyTo": "Records.personalNotes","type": "attribute","read": ["medicalAction"]}, {"applyTo": "Records.deleteOldRecords","type": "method","execute": ["administrate"]}, {"applyTo": "ds.authenticate","type": "method","promote":["hr"],"execute": ["guest"]} ] } }
アプリケーションでは、データクラス内で各ユーザーにロールを関連付ける必要があります (この例題では Users データクラス)。
Usersデータクラスは以下の通りです:
Usersデータクラスのサンプルデータです:
認証の段階で、ロールに関連付けられた権限を Web セッションに付与することができます。
以下は、authenticate() 関数の例です:
exposed Function authenticate($identifier : Text; $password : Text) : Text
var $user : cs.UsersEntity
Session.clearPrivileges()
$user:=ds.Users.query("identifier = :1"; $identifier).first()
If ($user#Null)
If (Verify password hash($password; $user.password))
Session.setPrivileges(New object("roles"; $user.role))
return $user.role+"として認証されました"
Else
return "ゲストとして認証されました"
End if
Else
return "ゲストとして認証されました"
End if
権限の詳細についてはドキュメントをよく読み、上記の HDI をダウンロードしてデモを実行してみてください。
フォーラムでは、ぜひ自由にディスカッションを開いてください。