diff --git a/docker-compose.yml b/docker-compose.yml index 2156c18..9247e17 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,40 +21,45 @@ services: fhir: depends_on: - mongo - image: imranq2/node-fhir-server-mongo:3.2.52 + - keycloak + image: imranq2/node-fhir-server-mongo:5.6.7 # To use local fhir code, comment above line and uncomment below # build: # dockerfile: Dockerfile # context: ../node-fhir-server-mongo environment: SERVER_PORT: 3000 - MONGO_HOSTNAME: mongo - MONGO_PORT: 27017 + MONGO_URL: mongodb://mongo:27017 + AUDIT_EVENT_MONGO_DB_NAME: fhir + AUDIT_EVENT_MONGO_URL: mongodb://mongo:27017 RESOURCE_SERVER: http://fhir:3000/ AUTH_SERVER_URI: http://myauthzserver.com - CLIENT_ID: client - CLIENT_SECRET: secret + AUTH_CUSTOM_SCOPE: "custom:scope" + CLIENT_ID: bwell-client-id + CLIENT_SECRET: bwell-secret INTROSPECTION_URL: https://myauthzserver.com/introspect MONGO_DB_NAME: fhir - CHECK_ACCESS_TAG_ON_SAVE: 1 - IS_PRODUCTION: + AUTH_CONFIGURATION_URI: http://keycloak:8080/realms/bwell-realm/.well-known/openid-configuration + AUTH_JWKS_URL: http://keycloak:8080/realms/bwell-realm/protocol/openid-connect/certs + EXTERNAL_AUTH_JWKS_URLS: http://keycloak:8080/realms/bwell-realm/protocol/openid-connect/certs RETURN_BUNDLE: "1" VALIDATE_SCHEMA: "1" - AUTH_ENABLED: "0" ENABLE_GRAPHQL: "1" + ENABLE_GRAPHQLV2: "1" LOGLEVEL: "DEBUG" SET_INDEX_HINTS: 0 CREATE_INDEX_ON_COLLECTION_CREATION: 1 - USE_TWO_STEP_SEARCH_OPTIMIZATION: "1" + USE_TWO_STEP_SEARCH_OPTIMIZATION: "0" + STREAM_RESPONSE: "1" ports: - '3000:3000' command: yarn start restart: on-failure healthcheck: - test: ["CMD-SHELL", "curl --silent --fail localhost:3000/health || exit 1"] + test: ["CMD-SHELL", "wget --spider --quiet localhost:3000/health || exit 1"] mongo: - image: mongo:5.0.15 + image: mongo:7.0.12 ports: - '27017:27017' environment: @@ -62,7 +67,43 @@ services: volumes: - mongo_data:/data/db healthcheck: - test: echo 'db.runCommand("ping").ok' | mongo mongo:27017/test --quiet + test: echo 'db.runCommand("ping").ok' | mongosh mongo:27017/test --quiet + + keycloak: + # https://github.com/keycloak/keycloak/releases + image: quay.io/keycloak/keycloak:25.0.1 + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: password + # The below settings are for test data creation + # This is the user and password that will be created in the realm + MY_ADMIN_USER_NAME: admin + MY_ADMIN_USER_PASSWORD: password + MY_ADMIN_USER_SCOPE: user/*.* access/*.* + MY_ADMIN_USER_TOKEN_USERNAME: admin + # This is the user and password that will be created in the realm + MY_USER_NAME: tester + MY_USER_PASSWORD: password + MY_USER_SCOPE: user/*.* access/*.* patient/*.read + MY_USER_TOKEN_USERNAME: tester + # This is the client setup + CLIENT_ID: bwell-client-id + CLIENT_SECRET: bwell-secret + # This is the service account that will be created in the realm + SERVICE_ACCOUNT_NAME: service-account + SERVICE_ACCOUNT_PASSWORD: password + SERVICE_ACCOUNT_SCOPE: user/*.* access/*.* + # These are the custom claims that will be added to any generated token + MY_USER_CLIENT_PERSON_ID: 0b2ad38a-20bc-5cf5-9739-13f242b05892 + MY_USER_CLIENT_PATIENT_ID: 22aa18af-af51-5799-bc55-367c22c85407 + MY_USER_BWELL_PERSON_ID: 0eb80391-0f61-5ce6-b221-a5428f2f38a7 + MY_USER_BWELL_PATIENT_ID: patient2 + KC_FEATURES: dynamic-scopes + ports: + - "8080:8080" + command: ["start-dev", "--import-realm", "--verbose"] + volumes: + - ./keycloak-config/realm-import.json:/opt/keycloak/data/import/realm-import.json volumes: mongo_data: diff --git a/keycloak-config/realm-import.json b/keycloak-config/realm-import.json new file mode 100644 index 0000000..20ff354 --- /dev/null +++ b/keycloak-config/realm-import.json @@ -0,0 +1,877 @@ +{ + "id": "bwell-realm", + "realm": "bwell-realm", + "enabled": true, + "defaultSignatureAlgorithm": "RS256", + "sslRequired": "none", + "defaultRole": { + "id": "1ba8bdad-53be-46c7-9a21-e173ee9bc904", + "name": "default-roles-bwell-realm", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "bwell-realm" + }, + "requiredCredentials": [ + "password" + ], + "users": [ + { + "id": "250614d3-26af-43cf-9eb6-b163e6b88a1f", + "username": "service-account-bwell-client-id", + "emailVerified": false, + "createdTimestamp": 1718982320512, + "enabled": true, + "totp": false, + "serviceAccountClientId": "bwell-client-id", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-bwell-realm" + ], + "clientRoles": { + "bwell-client-id": [ + "uma_protection" + ] + }, + "notBefore": 0, + "attributes": { + "custom:scope": "${SERVICE_ACCOUNT_SCOPE}", + "token_use": "access", + "username": "${SERVICE_ACCOUNT_NAME}" + } + }, + { + "id": "918ab7f3-cdd5-4f0c-b469-99782c9ac08a", + "username": "${MY_USER_NAME}", + "firstName": "Tester", + "lastName": "Tester", + "email": "tester@tester.com", + "emailVerified": true, + "createdTimestamp": 1718982320512, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "6dcd0803-6253-4b3c-909a-f545d54444d9", + "type": "password", + "createdDate": 1718987296927, + "value": "${MY_USER_PASSWORD}", + "temporary": false + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "notBefore": 0, + "realmRoles": [ + ], + "attributes": { + "custom:scope": "${MY_USER_SCOPE}", + "clientFhirPersonId": "${MY_USER_CLIENT_PERSON_ID}", + "clientFhirPatientId": "${MY_USER_CLIENT_PATIENT_ID}", + "bwellFhirPersonId": "${MY_USER_BWELL_PERSON_ID}", + "bwellFhirPatientId": "${MY_USER_BWELL_PATIENT_ID}", + "token_use": "access", + "username": "${MY_USER_TOKEN_USERNAME}" + } + }, + { + "id": "d3c80de8-6727-4a05-8f0f-59094edcc763", + "username": "${MY_ADMIN_USER_NAME}", + "firstName": "Tester", + "lastName": "Tester", + "email": "admin@tester.com", + "emailVerified": true, + "createdTimestamp": 1718982320512, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "6dcd0803-6253-4b3c-909a-f545d54444d8", + "type": "password", + "createdDate": 1718987296927, + "value": "${MY_ADMIN_USER_PASSWORD}", + "temporary": false + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "notBefore": 0, + "realmRoles": [ + ], + "attributes": { + "custom:scope": "${MY_ADMIN_USER_SCOPE}", + "token_use": "access", + "username": "${MY_ADMIN_USER_TOKEN_USERNAME}" + } + } + ], + "clients": [ + { + "id": "2297ca59-dbd5-48e3-92fd-f7aa865ce3c8", + "clientId": "${CLIENT_ID}", + "name": "${CLIENT_ID}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "${CLIENT_SECRET}", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": "1718982319", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "user", + "access", + "admin" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "resources": [], + "policies": [], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + }, + "protocolMappers": [ + { + "name": "client-person-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "clientFhirPersonId", + "claim.name": "clientFhirPersonId", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "client-patient-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "clientFhirPatientId", + "claim.name": "clientFhirPatientId", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "bwell-person-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "bwellFhirPersonId", + "claim.name": "bwellFhirPersonId", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "bwell-patient-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "bwellFhirPatientId", + "claim.name": "bwellFhirPatientId", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "bwell-token-use-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "token_use", + "claim.name": "token_use", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "bwell-username-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "claim.name": "username", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "custom-scope-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "custom:scope", + "claim.name": "custom:scope", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + } + ], + "clientScopes": [ + { + "id": "fce3b14c-783c-466e-8772-0fe981fe4149", + "name": "user", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "dynamic.scope.regexp": "^user*", + "display.on.consent.screen": "true", + "gui.order": "", + "is.dynamic.scope": "true", + "consent.screen.text": "" + } + }, + { + "id": "8f80fc64-1acb-430f-8528-6af6b015d38b", + "name": "access", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "dynamic.scope.regexp": "^access*", + "display.on.consent.screen": "true", + "gui.order": "", + "is.dynamic.scope": "true", + "consent.screen.text": "" + } + }, + { + "id": "4c6bfa15-aeeb-4914-b15f-34a78f2bcd6b", + "name": "admin", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "dynamic.scope.regexp": "^admin*", + "display.on.consent.screen": "true", + "gui.order": "", + "is.dynamic.scope": "true", + "consent.screen.text": "" + } + }, + { + "id": "b3edeb5f-c752-42c2-9d7b-536e07583f08", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "57111f79-4e2c-4bbf-8392-e5773bfa89b8", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "e4d14065-db45-4c0a-87dd-74e38db5ee96", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "6d59d0fa-9494-4af8-8004-b7c9b55a2b66", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "1d24e8a2-bdee-42fe-8a94-0c2dd619d0db", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ecea7d1c-6d31-4ce7-abe6-a6c3184b7111", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "41d5c9f2-6fdc-484e-a50e-756ae00d5352", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "f9674f0e-37d6-43f5-a71a-08ad80c0cbbf", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "1005c129-e616-4f26-a7f9-36dd5cbbd412", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "bbdad9af-6658-4793-8eb0-bc50cf720a53", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "45ff940e-d88b-4db2-9a64-7f3f645c3260", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "69e9771a-3001-4454-bbe8-3394fd9821e8", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "8b714f38-f5f2-4c25-8a60-060ae4dad571", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "94f2bf3b-0cc8-4dbe-bbf8-c698fe73aff2", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "8dec5b9b-d91b-4586-995a-a2de80a84bcb", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "59eaec60-0eec-4446-a1a5-e6bb879cf4c5", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "1663ad92-226a-4e73-a544-9981ad72e44d", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "76e4559c-02fe-4d6e-acd1-7f8c6cd1ca5d", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "750502b2-e02d-41f7-b895-b524706682a1", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "d0a63af3-d4b8-4468-bd99-bb1ebd364492", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "8c636f4b-85d5-4729-9ce5-930647202d94", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "65b15bc8-9b0d-4d7a-845d-32e0d84f9bf7", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "5c37777d-4bac-429a-a5c0-59664b1a6c44", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "f785cbc1-0d18-4dd4-af14-21b9c55f18a4", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "fc5bc010-df92-4dd1-b042-e224b40f87dd", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "49c761c3-567d-4abf-8c3c-bcfe306eb1aa", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "00ba032b-3285-4112-84a4-ecb1ade4fb10", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "ea012eb0-9ad3-470c-b952-d67eceb8e64e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "55291efc-a200-453c-ab6f-df4f4bfe5a90", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "267a3f6a-ebca-49ef-a315-31aaa5225dd7", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "d776da80-3d56-4919-bd9c-62e65b3a392f", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "5fd8b5d0-1c05-4961-860e-a67dd06d6351", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "ac6a0497-668e-4331-a032-bd2000fc10f1", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "a218dc68-1aba-485c-9eb6-a2e1485f86db", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "bf6043fd-8600-4afe-bab3-60b886fd8127", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "a09e5393-2167-4337-bed8-f632792d0d5e", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + }, + { + "id": "9bf211e0-e46c-4f6b-acbd-e5986105bdd6", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "d366238c-0299-48e9-9ed2-bca9fb8498a7", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "01b32a0a-1fc0-4014-b736-0ec3579134c1", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "a61ec6f4-2d66-4756-979f-81712fb614a8", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + } + ] +} diff --git a/library/features/doctor_feature/practitioner/v1/test/test_doctor_feature_practitioner.py b/library/features/doctor_feature/practitioner/v1/test/test_doctor_feature_practitioner.py index 2478c25..3bfa882 100644 --- a/library/features/doctor_feature/practitioner/v1/test/test_doctor_feature_practitioner.py +++ b/library/features/doctor_feature/practitioner/v1/test/test_doctor_feature_practitioner.py @@ -1,6 +1,7 @@ from pathlib import Path from mockserver_client.mockserver_client import MockServerFriendlyClient +import requests from spark_pipeline_framework.logger.yarn_logger import get_logger from pyspark.sql import SparkSession @@ -31,6 +32,21 @@ def test_doctor_feature_practitioner(spark_session: SparkSession) -> None: "mock_server_url": mock_server_url, } + fhir_server_auth_config = requests.get( + url="http://fhir:3000/.well-known/smart-configuration" + ) + assert ( + fhir_server_auth_config.status_code == 200 + ), f"{fhir_server_auth_config.status_code}: {fhir_server_auth_config.text}" + access_token_response = requests.post( + url=fhir_server_auth_config.json()["token_endpoint"], + data="grant_type=client_credentials&client_id=bwell-client-id&client_secret=bwell-secret", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + assert ( + access_token_response.status_code == 200 + ), f"{access_token_response.status_code}: {access_token_response.text}" + # initialize the mock calls test_fhir.initialize( test_name=test_name, @@ -46,6 +62,7 @@ def test_doctor_feature_practitioner(spark_session: SparkSession) -> None: mock_server_url=mock_server_url, test_name=test_name, fhir_validation_url="http://fhir:3000/4_0_0", + fhir_server_access_token=f"{access_token_response.json()['access_token']}", ) SparkPipelineFrameworkTestRunnerV2( diff --git a/spark_pipeline_framework_testing/validators/fhir_validator.py b/spark_pipeline_framework_testing/validators/fhir_validator.py index e776569..91be662 100644 --- a/spark_pipeline_framework_testing/validators/fhir_validator.py +++ b/spark_pipeline_framework_testing/validators/fhir_validator.py @@ -36,6 +36,7 @@ def __init__( mock_server_url: str, test_name: str, fhir_validation_url: Optional[str] = None, + fhir_server_access_token: Optional[str] = None, ) -> None: super().__init__(related_inputs=related_inputs) self.related_file_inputs = ( @@ -48,6 +49,7 @@ def __init__( assert test_name self.test_name: str = test_name self.fhir_validation_url: Optional[str] = fhir_validation_url + self.fhir_server_access_token: Optional[str] = fhir_server_access_token def validate( self, @@ -94,6 +96,7 @@ def validate( # validate the resource self.validate_resource( fhir_validation_url=self.fhir_validation_url, + fhir_server_access_token=self.fhir_server_access_token, resource_dict=row_dict, resource_type=resource_type, ) @@ -143,6 +146,7 @@ def validate( @staticmethod def validate_resource( fhir_validation_url: str, + fhir_server_access_token: Optional[str], resource_dict: Dict[str, Any], resource_type: str, ) -> None: @@ -150,6 +154,8 @@ def validate_resource( assert resource_type full_uri /= resource_type headers = {"Content-Type": "application/fhir+json"} + if fhir_server_access_token: + headers["Authorization"] = f"Bearer {fhir_server_access_token}" full_uri /= "$validate" json_payload: str = json.dumps(resource_dict) json_payload_bytes: bytes = json_payload.encode("utf-8") diff --git a/tests/library/features/v2/doctor_feature/practitioner_fail_on_fhir_validation/v1/test_practitioner_fail_on_fhir_validation.py b/tests/library/features/v2/doctor_feature/practitioner_fail_on_fhir_validation/v1/test_practitioner_fail_on_fhir_validation.py index c0f4014..3e992bc 100644 --- a/tests/library/features/v2/doctor_feature/practitioner_fail_on_fhir_validation/v1/test_practitioner_fail_on_fhir_validation.py +++ b/tests/library/features/v2/doctor_feature/practitioner_fail_on_fhir_validation/v1/test_practitioner_fail_on_fhir_validation.py @@ -2,6 +2,7 @@ import pytest from mockserver_client.mockserver_client import MockServerFriendlyClient +import requests from spark_pipeline_framework.logger.yarn_logger import get_logger from spark_pipeline_framework_testing.validators.fhir_validator import FhirValidator @@ -40,12 +41,28 @@ def test_practitioner_fail_on_fhir_validation(spark_session: SparkSession) -> No "mock_server_url": mock_server_url, } + fhir_server_auth_config = requests.get( + url="http://fhir:3000/.well-known/smart-configuration" + ) + assert ( + fhir_server_auth_config.status_code == 200 + ), f"{fhir_server_auth_config.status_code}: {fhir_server_auth_config.text}" + access_token_response = requests.post( + url=fhir_server_auth_config.json()["token_endpoint"], + data="grant_type=client_credentials&client_id=bwell-client-id&client_secret=bwell-secret", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + assert ( + access_token_response.status_code == 200 + ), f"{access_token_response.status_code}: {access_token_response.text}" + test_validator = FhirValidator( related_inputs=test_fhir, related_file_inputs=test_input, mock_server_url=mock_server_url, test_name=test_name, fhir_validation_url="http://fhir:3000/4_0_0", + fhir_server_access_token=f"{access_token_response.json()['access_token']}", ) with pytest.raises(AssertionError, match=r"Failed validation for resource*"):