diff --git a/demo/keycloak/eoapi-realm.json b/demo/keycloak/eoapi-realm.json new file mode 100644 index 0000000..4deb09f --- /dev/null +++ b/demo/keycloak/eoapi-realm.json @@ -0,0 +1,2281 @@ +{ + "id": "c2c7ef25-d119-422d-a3bd-9abda8dc1385", + "realm": "eoapi", + "displayName": "eoAPI", + "displayNameHtml": "", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "2234c924-e3f6-40f5-83d1-18ed7a2a6bf6", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "c2c7ef25-d119-422d-a3bd-9abda8dc1385", + "attributes": {} + }, + { + "id": "7a442698-ea19-46f0-ada5-b75080ba4049", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "c2c7ef25-d119-422d-a3bd-9abda8dc1385", + "attributes": {} + }, + { + "id": "d31348b3-11f8-4359-a06a-f0088e255544", + "name": "default-roles-eoapi", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ] + }, + "clientRole": false, + "containerId": "c2c7ef25-d119-422d-a3bd-9abda8dc1385", + "attributes": {} + }, + { + "id": "8c697428-53bb-4946-a452-4aaa8f6a0c63", + "name": "Data Admin", + "description": "Users who are allowed to make any changes to the data within our system.", + "composite": false, + "clientRole": false, + "containerId": "c2c7ef25-d119-422d-a3bd-9abda8dc1385", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "0a0db158-8e13-4178-9c7c-18062b2b724f", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "d72b3d32-65da-4307-9dd8-094865e544d5", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "6bc5a7a6-761b-447e-9b1f-875ee7eeadfd", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "06d5107b-939b-4d9b-907c-b2c087a5ac9b", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "815e78e3-9170-4945-ade0-f95cca1d71ca", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "760b0069-1fc8-41ea-8aea-2d97daae05ef", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "f26fc8b9-f5b6-479d-ad4b-1a198f6a67ac", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "e4d1488e-2507-4379-a22f-d586131946e8", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "2420dd01-5b37-4c8e-96e4-e75c195fef17", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "25f05071-0c1d-4241-8210-a24dcdaa6941", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "86498503-5be9-4fb3-87ad-30de2a8b893a", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "c5f13fa0-0f3e-4594-b95e-589149575e75", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "dfb96a07-936c-4eac-9c97-58d6508b2c37", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "0c21ab61-261a-4e09-bd99-2ac587381401", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-authorization", + "manage-realm", + "query-users", + "query-clients", + "view-clients", + "query-groups", + "view-identity-providers", + "view-events", + "impersonation", + "manage-events", + "view-authorization", + "view-realm", + "manage-clients", + "manage-identity-providers", + "create-client", + "query-realms", + "view-users", + "manage-users" + ] + } + }, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "42f6800e-a785-493d-9d1a-0653a0f68906", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "9810b658-c088-42bc-9ee2-d620e2c2404e", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "8587c1e4-1448-4256-b21c-0b2848ba4b4c", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "7c518d4e-5ee9-4a94-8966-78201522718b", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + }, + { + "id": "7de01164-01b9-4686-b5e5-db61d77ad783", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "371837ef-6421-465d-ab3d-af8a124004fe", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "stac-api": [ + { + "id": "dcc211f7-c4b3-4e00-9e71-c1208fbba4e6", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "bd7dbeb9-7d8d-4177-87f4-2f8360bcdbdb", + "attributes": {} + } + ], + "account-console": [], + "broker": [], + "account": [ + { + "id": "5a0701f0-be91-47f6-99a2-f77126c42749", + "name": "view-groups", + "composite": false, + "clientRole": true, + "containerId": "83529886-e162-4a1f-b240-b1b34b4ab5f3", + "attributes": {} + }, + { + "id": "5cf4871e-6fa9-4614-96e0-349cd329aed2", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "83529886-e162-4a1f-b240-b1b34b4ab5f3", + "attributes": {} + }, + { + "id": "dbaeefcd-1977-473f-9699-0d058a9ab96e", + "name": "manage-account", + "composite": false, + "clientRole": true, + "containerId": "83529886-e162-4a1f-b240-b1b34b4ab5f3", + "attributes": {} + } + ] + } + }, + "groups": [ + { + "id": "cbb8524e-8844-4457-8770-0bbcf67319d9", + "name": "Admins", + "path": "/Admins", + "subGroups": [], + "attributes": {}, + "realmRoles": [ + "Data Admin" + ], + "clientRoles": {} + } + ], + "defaultRole": { + "id": "d31348b3-11f8-4359-a06a-f0088e255544", + "name": "default-roles-eoapi", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "c2c7ef25-d119-422d-a3bd-9abda8dc1385" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "scopeMappings": [ + { + "clientScope": "stac:item:create", + "roles": [ + "Data Admin" + ] + }, + { + "clientScope": "stac:item:delete", + "roles": [ + "Data Admin" + ] + }, + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + }, + { + "clientScope": "stac:collection:create", + "roles": [ + "Data Admin" + ] + }, + { + "clientScope": "stac:item:update", + "roles": [ + "Data Admin" + ] + }, + { + "clientScope": "stac:collection:delete", + "roles": [ + "Data Admin" + ] + }, + { + "clientScope": "stac:collection:update", + "roles": [ + "Data Admin" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "83529886-e162-4a1f-b240-b1b34b4ab5f3", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/eoapi/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/eoapi/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "dd776235-74b2-4daf-afeb-378fc0e1f1b7", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/eoapi/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/eoapi/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9847e750-9f4b-4a61-a7a0-9d5a0b40b30e", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "9ca22f0f-1143-42c0-92b3-023f8fbe2f85", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "50ef1475-6b0d-4424-bd4a-1963188956ac", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "371837ef-6421-465d-ab3d-af8a124004fe", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "1d855b09-0d5d-460e-8627-3b21c884c71c", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/eoapi/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/eoapi/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "7a9399cc-4264-498d-8eec-c93799a04c77", + "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" + } + } + ], + "defaultClientScopes": [], + "optionalClientScopes": [] + }, + { + "id": "bd7dbeb9-7d8d-4177-87f4-2f8360bcdbdb", + "clientId": "stac-api", + "name": "STAC FastAPI", + "description": "", + "rootUrl": "http://localhost:8081", + "adminUrl": "http://localhost:8081", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": true, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://localhost:8081/docs/oauth2-redirect" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": "1712093046", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "use.jwks.url": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "tls.client.certificate.bound.access.tokens": "false", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "678da14f-aeb5-4d59-9513-1a03396f5b31", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "390a26ba-573c-4277-a531-7da030a4647e", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "ff01b5e4-3f78-4bb1-9de2-3af3eaed06de", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "stac:item:delete", + "acr", + "stac:item:create", + "profile", + "roles", + "stac:collection:create", + "stac:collection:delete", + "stac:item:update", + "stac:collection:update", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "aaa63438-7bc1-491b-9afb-db3732d8eff5", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "48a86c30-c838-461b-8e91-c3e4ab43c7a5", + "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": "762cc6a3-23fc-4f3c-a167-9924dc510f1d", + "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": "71639e89-8e29-4d03-8c1d-747c980393b1", + "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": "26805f54-c52c-4ee7-bcea-f813e2f9b2c6", + "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": "a7b211ce-579d-405c-a2b1-5f037b2219aa", + "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": "1c3ae0c1-02ca-48d3-8f13-6b2047e3db1f", + "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": "69058270-d423-4ead-91e4-99184939c331", + "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": "7a2c8850-e91f-44c8-b7f1-9164944ddc77", + "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": "9713d1ce-921d-4f48-9297-5fa583bc58a2", + "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": "f96d0536-ddcc-4437-adc0-deced3337d35", + "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": "025c6a7e-fb17-44ee-9220-8cb828a0c1f1", + "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": "03f33a20-887e-4678-909a-c6637ea1258d", + "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": "ff2b6e63-5c2c-4f35-bec3-a18a5921d276", + "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": "ca6e86bf-6bbd-44eb-8edf-6baf64186e37", + "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": "45a21cc4-dc79-467a-8b62-19b9318d93b5", + "name": "stac:item:create", + "description": "Ability to create STAC items", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "cd343b34-d750-4397-bd77-82cd23cba2e8", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "00d776a3-b29e-4ca9-8093-8dcd41191514", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "26581b1e-19cd-4c38-b515-def5046e3fd5", + "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", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "dbc3071b-6b3d-4dbb-9bf2-5e6d88f7b526", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "4baf7214-a062-4a64-a07c-26f653e04d4a", + "name": "stac:item:delete", + "description": "Ability to delete STAC items", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "3c9ae02e-b43c-4e3d-89a7-525323914765", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "91def80e-425a-4ad6-8d00-60c34c99ab11", + "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": "99cfdf1a-9816-4ae0-854e-affa983e5842", + "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": "26109644-b157-4c9f-afd9-36db9a82a01f", + "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": "e51f794b-717b-4cda-b481-961faf72fb81", + "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", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "286e2070-c2e2-4767-bff6-37feb1018516", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "5dca059c-e881-431e-a253-e9fde1a8c021", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "8fe3e1e1-a1c2-4d13-a395-52ffb418f506", + "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": "aae2631f-ce9f-4133-baa4-a23e9b1563b6", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3142890a-95d4-4e21-9293-a665f5fb6669", + "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" + } + }, + { + "id": "bbb5b579-38a7-4469-8beb-da2eee193582", + "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": "47451a2f-e60f-47fa-b643-15c7e128bad0", + "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": "87ffde83-dc54-4129-a982-834e53911e06", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b0f58df6-d221-41aa-b1e2-cc1009d291a7", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String" + } + }, + { + "id": "36f88376-2143-488a-8b73-130bb5c653be", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "id": "55c8f0d3-210a-465c-b45a-2d090d1a0628", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "127420f1-cc96-4bb0-9bcb-c86a39b92507", + "name": "stac:collection:create", + "description": "Ability to create STAC collections", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "21c9767a-9941-43ee-94c9-e99fc9e7b556", + "name": "stac:item:update", + "description": "Ability to update STAC items", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "44058cfa-4682-46be-9cea-3508535c7ca5", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "167b5f31-cf6c-48ce-90a2-a71c12a60afe", + "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": "b2d5a08e-1db4-4b2a-9fec-133bc0afc8d4", + "name": "stac:collection:delete", + "description": "Ability to delete STAC collections", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "89b1954a-d774-4297-9445-a915805cacdb", + "name": "stac:collection:update", + "description": "Ability to update STAC collections", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "stac:collection:delete", + "stac:collection:update", + "stac:item:create", + "stac:item:delete", + "stac:item:update", + "stac:collection:create" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "010f4018-9f2e-4365-bad3-bb2aa8920fe3", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "1d862a6c-52f5-4123-95e2-fab5ef85a1df", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "598a11e9-67fc-43bb-b8c7-3d2fc1b5c12a", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "9b12bbb9-965e-48f7-9987-79e6277c3328", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper" + ] + } + }, + { + "id": "02439a09-4a81-46e6-b9a3-283d88823711", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "bbbc8d48-4dd4-482c-bb7a-b95d67efa11a", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "941dd093-2359-4969-a2bf-e2e6c648c386", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "84666d31-16db-445f-9ae8-9eb6d9c9ebe4", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "3f55bd50-d688-45fe-942d-a90773baf5f6", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "ee4a72ff-29e2-4edf-b4b0-7042944d98cc", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "id": "5c49cec8-6084-42de-951d-0f0c5199948a", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "75776c7d-3b45-4d00-9a54-e74e1554246b", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "e475548b-090c-47aa-91e4-521a19185599", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "e4060969-3072-4fe5-adf3-cd37ffefee6e", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "5b5a49a7-2925-4564-90db-8a0044e3c79e", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "352c71c9-983f-448e-ae7e-68f6094ea1ed", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "2c7628ed-5e77-4b8f-9b6d-6ea0aa540d72", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "658225bf-706d-426f-8826-5fdddad83de2", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "81979bb5-c98b-447a-bc6d-4d9d9ba12409", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "ce28e9a6-5525-4172-82df-727576177ea0", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "89072fc0-edc2-46a5-9c4e-a497a0571a2d", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "e7f0292b-68ad-48e4-8b2d-25259452fd84", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "14203f46-9c2b-47b5-a78d-f306ea88f6ba", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "6e1f2129-8e6d-4a9f-8eb3-e0c54c76d72d", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "cf274330-1df3-42f2-800c-917839904ec0", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "c36af9a8-4fe5-421d-8134-8ce0b7178949", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "db0a99a9-b4d3-41eb-aa02-b5b72a96675b", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "9c3a6f0c-41e0-4813-ae0f-fe49c29477f4", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d199947e-d5e4-44e1-a9cf-4cf66df9f5ea", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "982de824-a043-4890-9fcf-5665d414db98", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "9ce88b6d-2f87-49e9-9c3b-afe5180d1971", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "0fa082e6-46f2-419e-b605-667659960a4c", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "", + "acr.loa.map": "{}" + }, + "keycloakVersion": "24.0.2", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/demo/keycloak/eoapi-users-0.json b/demo/keycloak/eoapi-users-0.json new file mode 100644 index 0000000..31b5030 --- /dev/null +++ b/demo/keycloak/eoapi-users-0.json @@ -0,0 +1,57 @@ +{ + "realm": "eoapi", + "users": [ + { + "id": "59c5407b-6280-48b6-ae19-64fe593089c7", + "username": "alice", + "firstName": "Alice", + "lastName": "de Admin", + "email": "alice@example.com", + "emailVerified": true, + "createdTimestamp": 1712037625377, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "78736ca9-4091-434f-a6cd-cd42834f6b40", + "type": "password", + "userLabel": "Super insecure password", + "createdDate": 1712038488886, + "secretData": "{\"value\":\"l8IN1eQ+y+F/BSJ9joEayhaGlQkL/peHedxs/mUNnjWbR/16wTl7VJHXzTJ00A7X9ufyeh0ytRFuC+wLtIy/Kg==\",\"salt\":\"26MxYn2HeZIQPdiszZz7Cg==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-eoapi"], + "notBefore": 0, + "groups": ["/Admins"] + }, + { + "id": "ebe7613f-377e-416d-9ef5-c990c5ddbe66", + "username": "bob", + "firstName": "Bob", + "lastName": "Dubois", + "email": "bob@example.com", + "emailVerified": true, + "createdTimestamp": 1712037633309, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "d0068d70-f38d-4b16-b431-359ac37fca4e", + "type": "password", + "userLabel": "Super insecure password", + "createdDate": 1712038506484, + "secretData": "{\"value\":\"Zq6E843f5+pcCcznbh82JHPgy634GWhYFHfc74mVJc23mlVEmwslkBPT+czD7+fa5InyGWLPza5M1nX6FISYbA==\",\"salt\":\"ERYVR7EUVeqHywNi5wKMTA==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-eoapi"], + "notBefore": 0, + "groups": [] + } + ] +} \ No newline at end of file diff --git a/docker-compose.custom.yml b/docker-compose.custom.yml index 09880d1..add2e0b 100644 --- a/docker-compose.custom.yml +++ b/docker-compose.custom.yml @@ -20,6 +20,8 @@ services: build: context: . dockerfile: dockerfiles/Dockerfile.stac + args: + - INSTALL_EXTRA=[jwt] ports: - "${MY_DOCKER_IP:-127.0.0.1}:8081:8081" environment: @@ -49,6 +51,10 @@ services: # PgSTAC extensions # - EOAPI_STAC_EXTENSIONS=["filter", "query", "sort", "fields", "pagination", "context", "transaction"] # - EOAPI_STAC_CORS_METHODS='GET,POST,PUT,OPTIONS' + - KEYCLOAK_REALM=eoapi + - KEYCLOAK_HOST=http://localhost:8080 + - KEYCLOAK_CLIENT_ID=stac-api + - KEYCLOAK_INTERNAL_HOST=http://keycloak:8080 depends_on: - database - raster @@ -276,6 +282,20 @@ services: command: postgres -N 500 volumes: - ./.pgdata:/var/lib/postgresql/data + + keycloak: + profiles: + - keycloak + image: quay.io/keycloak/keycloak:latest + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + ports: + - "8080:8080" + - "9990:9990" + command: start-dev --import-realm + volumes: + - ./demo/keycloak:/opt/keycloak/data/import networks: default: diff --git a/dockerfiles/Dockerfile.stac b/dockerfiles/Dockerfile.stac index 287f9be..643f66c 100644 --- a/dockerfiles/Dockerfile.stac +++ b/dockerfiles/Dockerfile.stac @@ -2,11 +2,12 @@ ARG PYTHON_VERSION=3.11 FROM ghcr.io/vincentsarago/uvicorn-gunicorn:${PYTHON_VERSION} +ARG INSTALL_EXTRA= + ENV CURL_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt -COPY runtime/eoapi/stac /tmp/stac -RUN pip install /tmp/stac -RUN rm -rf /tmp/stac +COPY runtime/eoapi/stac /app/stac +RUN pip install -e "/app/stac${INSTALL_EXTRA}" ENV MODULE_NAME eoapi.stac.app ENV VARIABLE_NAME app diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b7d5738..1a90c23 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -8,6 +8,9 @@ repo_name: "developmentseed/eoAPI" repo_url: "https://github.com/developmentseed/eoAPI" extra: + analytics: + provider: plausible + domain: eoapi.dev social: - icon: "fontawesome/brands/github" link: "https://github.com/developmentseed" diff --git a/docs/src/overrides/partials/integrations/analytics/plausible.html b/docs/src/overrides/partials/integrations/analytics/plausible.html new file mode 100644 index 0000000..d8f972a --- /dev/null +++ b/docs/src/overrides/partials/integrations/analytics/plausible.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/runtime/eoapi/stac/eoapi/stac/app.py b/runtime/eoapi/stac/eoapi/stac/app.py index da1b8d0..2e233c2 100644 --- a/runtime/eoapi/stac/eoapi/stac/app.py +++ b/runtime/eoapi/stac/eoapi/stac/app.py @@ -1,11 +1,12 @@ """FastAPI application using PGStac.""" from contextlib import asynccontextmanager +from typing import Annotated, Any, Dict from eoapi.stac.config import ApiSettings, TilesApiSettings from eoapi.stac.extension import TiTilerExtension from eoapi.stac.extension import extensions_map as PgStacExtensions -from fastapi import FastAPI +from fastapi import FastAPI, Security from fastapi.responses import ORJSONResponse from stac_fastapi.api.app import StacApi from stac_fastapi.api.models import create_get_request_model, create_post_request_model @@ -19,6 +20,8 @@ from starlette.templating import Jinja2Templates from starlette_cramjam.middleware import CompressionMiddleware +from .auth import KeycloakAuth + try: from importlib.resources import files as resources_files # type: ignore except ImportError: @@ -32,6 +35,8 @@ tiles_settings = TilesApiSettings() settings = Settings() +keycloak = KeycloakAuth() + @asynccontextmanager async def lifespan(app: FastAPI): @@ -54,7 +59,15 @@ async def lifespan(app: FastAPI): GETModel = create_get_request_model(extensions) api = StacApi( - app=FastAPI(title=api_settings.name, lifespan=lifespan), + app=FastAPI( + title=api_settings.name, + lifespan=lifespan, + swagger_ui_init_oauth={ + "appName": "eoAPI", + "clientId": keycloak.client_id, + "usePkceWithAuthorizationCodeGrant": True, + }, + ), title=api_settings.name, description=api_settings.name, settings=settings, @@ -82,6 +95,25 @@ async def lifespan(app: FastAPI): extension = TiTilerExtension() extension.register(api.app, tiles_settings.titiler_endpoint) +for (method, path), scopes in { + ("POST", "/collections"): ["stac:collection:create"], + ("PUT", "/collections"): ["stac:collection:update"], + ("DELETE", "/collections/{collection_id}"): ["stac:collection:delete"], + ("POST", "/collections/{collection_id}/items"): ["stac:item:create"], + ("PUT", "/collections/{collection_id}/items/{item_id}"): ["stac:item:update"], + ("DELETE", "/collections/{collection_id}/items/{item_id}"): ["stac:item:delete"], +}.items(): + api.add_route_dependencies( + [ + { + "path": app.router.prefix + path, + "method": method, + "type": "http", + }, + ], + [Security(keycloak.scheme, scopes=scopes)], + ) + @app.get("/index.html", response_class=HTMLResponse) async def viewer_page(request: Request): @@ -91,3 +123,9 @@ async def viewer_page(request: Request): {"request": request, "endpoint": str(request.url).replace("/index.html", "")}, media_type="text/html", ) + + +@app.get("/user", tags=["auth"]) +def get_user(user_token: Annotated[Dict[Any, Any], Security(keycloak.user_validator)]): + """View auth token.""" + return user_token diff --git a/runtime/eoapi/stac/eoapi/stac/auth.py b/runtime/eoapi/stac/eoapi/stac/auth.py new file mode 100644 index 0000000..50b044b --- /dev/null +++ b/runtime/eoapi/stac/eoapi/stac/auth.py @@ -0,0 +1,144 @@ +""" +Auth module for Keycloak integration. +""" + +from functools import cached_property +from typing import Annotated, Dict, Iterable, List, Optional, TypedDict + +import jwt +import pydantic +from fastapi import HTTPException, Security, security, status + + +class KeycloakAuth(pydantic.BaseSettings): + """ + Keycloak Integration. + """ + + realm: str + host: str + client_id: str + internal_host: Optional[str] = None + + required_audience: Optional[str | Iterable[str]] = None + scopes: Dict[str, str] = pydantic.Field(default_factory=lambda: {}) + + class Config: + """Pydantic Config""" + + env_file = ".env" + env_prefix = "KEYCLOAK_" + keep_untouched = (cached_property,) + + def _build_url(self, host: str): + return f"{host}/realms/{self.realm}/protocol/openid-connect" + + @property + def user_validator( + self, + ): + """ + FastAPI Security Dependency to validate auth token. + """ + + def valid_user_token( + token_str: Annotated[str, Security(self.scheme)], + required_scopes: security.SecurityScopes, + ) -> TokenPayload: + # Parse & validate token + try: + token = jwt.decode( + token_str, + self.jwks_client.get_signing_key_from_jwt(token_str).key, + algorithms=["RS256"], + audience=self.required_audience, + ) + except jwt.exceptions.InvalidTokenError as e: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=f"Could not validate credentials: {e}", + headers={"WWW-Authenticate": "Bearer"}, + ) from e + + # Validate scopes (if required) + for scope in required_scopes.scopes: + if scope not in token["scope"]: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Not enough permissions", + headers={ + "WWW-Authenticate": f'Bearer scope="{required_scopes.scope_str}"' + }, + ) + + return token + + return valid_user_token + + @property + def internal_keycloak_api(self): + """ + URL for requests to Keycloak made from within this service. + + e.g. When fetching JWKS keys. + """ + return self._build_url(self.internal_host or self.host) + + @property + def keycloak_api(self): + """ + URL for requests to Keycloak made from outside this service. + + e.g. When performing OAuth2 Authorization Code flow from docs UI. + """ + return self._build_url(self.host) + + @property + def scheme(self): + """ + FastAPI Security Scheme. + """ + return security.OAuth2AuthorizationCodeBearer( + authorizationUrl=f"{self.keycloak_api}/auth", + tokenUrl=f"{self.keycloak_api}/token", + scopes=self.scopes, + ) + + @cached_property + def jwks_client(self): + """ + PyJWKClient instance for fetching JWKS keys from Keycloak. Used when validating + JWTs. + """ + return jwt.PyJWKClient(f"{self.internal_keycloak_api}/certs") + + +class RealmAccess(TypedDict): + """Realm Access.""" + + roles: List[str] + + +class TokenPayload(TypedDict): + """Parsed Keycloak JWT""" + + exp: int + iat: int + auth_time: int + jti: str + iss: str + sub: str + typ: str + azp: str + session_state: str + acr: str + allowed_origins: List[str] + realm_access: RealmAccess + scope: str + sid: str + email_verified: bool + name: str + preferred_username: str + given_name: str + family_name: str + email: str diff --git a/runtime/eoapi/stac/eoapi/stac/config.py b/runtime/eoapi/stac/eoapi/stac/config.py index 7795b56..22654ef 100644 --- a/runtime/eoapi/stac/eoapi/stac/config.py +++ b/runtime/eoapi/stac/eoapi/stac/config.py @@ -22,6 +22,7 @@ class _ApiSettings(pydantic.BaseSettings): "fields", "pagination", "context", + "transaction", ] @pydantic.validator("cors_origins") diff --git a/runtime/eoapi/stac/pyproject.toml b/runtime/eoapi/stac/pyproject.toml index 5313c66..96ab940 100644 --- a/runtime/eoapi/stac/pyproject.toml +++ b/runtime/eoapi/stac/pyproject.toml @@ -40,6 +40,10 @@ test = [ "pytest-asyncio", "httpx", ] +jwt = [ + "pyjwt", + "cryptography" +] [build-system] requires = ["pdm-pep517"]