Init Aidbox configuration project
Copy cd acl && touch system.edn
Populate configuration file
Copy {:ns acl.system
:import #{aidbox
aidbox.config
aidbox.rest
aidbox.rest.acl}
search-config
{:zen/tags #{aidbox.config/search}
:zen-fhir :disable
:resource-compat false
:engine :knife
:fhir-comparisons true
:default-params {:timeout 30
:total "none"
:count 100}
:chain {:subselect true}}
compatibility-config
{:zen/tags #{aidbox.config/compatibility}
:validation {:json-schema {:regex #{:fhir-datetime}}}
:auth {:pkce {:code-challenge {:s256 {:conformant true}}}}}
zen-config
{:zen/tags #{aidbox.config/config}
:search search-config
:compatibility compatibility-config
:fhir-version "4.0.1"
:compliant-mode-enabled? true
:override-createdat-url "http://fhir.aidbox.app/extension/createdat"
:correct-aidbox-format true
:disable-legacy-seed true}
seed-data
{:zen/tags #{aidbox/service}
:engine aidbox/seed-v2
:resources
{:Client {:root {:secret "secret"
:first_party true
:grant_types ["client_credentials" "basic"]}
:postman {:secret "secret"
:grant_types ["password" "basic"]}}
:Organization {:org-1 {:resourceType "Organization" :id "org-1"}
:org-2 {:resourceType "Organization" :id "org-2"}}
:User {:admin {:password "password"}
:admin-org-1 {:password "password" :organization {:resourceType "Organization" :id "org-1"}}
:user-org-1 {:organization {:resourceType "Organization" :id "org-1"}}
:user-org-2 {:organization {:resourceType "Organization" :id "org-2"}}}
:AccessPolicy {:admin-seed-policy
{:engine "allow"
:link [{:resourceType "User" :id "admin"}
{:resourceType "Client" :id "root"}]}
:org-admin-can-read-its-users
{:engine "matcho"
:roleName "OrganizationAdmin"
:matcho {:uri "#/User.+" :request-method "get"}}}
:Role {:OrganizationAdmin {:name "OrganizationAdmin"
:user {:resourceType "User" :id "admin-org-1"}}}}}
;; define additional filtering rules based
;; org-id-param value is taken from the requester user resource accroding to the :path
org-id-param
{:zen/tags #{aidbox.rest.acl/request-param}
:source-schema {:type zen/string}
:path [:user :organization :id]}
;; defined SQL statement injecting the org-id-param value
org-condition
{:zen/tags #{aidbox.rest.acl/sql-template}
:params {:org-id org-id-param}
:template "{{target-resource}}#>>'{organization,id}'={{params.org-id}}"}
;; attach injected SQL to the filter
user-filter
{:zen/tags #{aidbox.rest.acl/filter}
:expression org-condition}
;; create user-read operation & apply user-filer
user-read
{:zen/tags #{aidbox.rest/op}
:engine aidbox.rest.acl/read
:resource "User"
:format "aidbox"
:filter user-filter}
;; rebing standard User read operation
user-api
{:zen/tags #{aidbox.rest/api}
"User" {[:id] {:GET user-read}}}
;; attach api to the server
server
{:zen/tags #{aidbox/service}
:engine aidbox/http
:apis #{user-api}}
box
{:zen/tags #{aidbox/system}
:config zen-config
:services {:seed seed-data
:http server}}}
Check configuration works
Get access token for admin-org-1
user
request access_token for user admin-org-1 response status: 200
Copy POST [base-url]/auth/token
Content-Type: text/yaml
{
"client_id": "postman",
"client_secret": "secret",
"username": "admin-org-1",
"password": "password",
"grant_type": "password"
}
Copy token_type: Bearer
userinfo:
organization: { id: org-1, resourceType: Organization }
id: admin-org-1
resourceType: User
sub: admin-org-1
need_patient_banner: true
access_token: MW...Ex
Read user belonging to the requester organization
fetch allowed user data response status: 200
Copy GET [base-url]/User/user-org-1
Content-Type: text/yaml
authorization: "Bearer MW...Ex"
Copy organization: { id: org-1, resourceType: Organization }
id: user-org-1
resourceType: User
Read attempt user from the different organization
fetch allowed user data response status: 404
Copy GET [base-url]/User/user-org-2
Content-Type: text/yaml
authorization: "Bearer MW...Ex"
Copy resourceType: OperationOutcome
id: deleted
text:
status: generated
div: "Resource User/user-org-2 not found"
issue:
- severity: fatal
code: deleted
diagnostics: "Resource User/user-org-2 not found"