diff --git a/LICENSE.txt b/LICENSE.txt
index de8dfbd..968fc66 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2023-2024 Tracehub
+Copyright (c) 2023-2024 Tracehub.git
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to read
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index ffcd09a..46915c5 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -1,6 +1,6 @@
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ org.liquibase
+ liquibase-core
+
+
org.postgresql
postgresql
@@ -72,11 +99,31 @@ SOFTWARE.
jcabi-http
${jcabi-http.version}
+
+ com.jcabi
+ jcabi-jdbc
+ ${jcabi-jdbc.version}
+
+
+ com.jcabi
+ jcabi-github
+ ${jcabi-github.version}
+
+
+ org.glassfish.jersey.core
+ jersey-common
+ ${jersey-common.version}
+
org.cactoos
cactoos
${cactoos.version}
+
+ io.github.eo-cqrs
+ eokson
+ ${eokson.version}
+
org.springframework.boot
spring-boot-starter-test
@@ -89,6 +136,23 @@ SOFTWARE.
test
+
+ com.github.dasniko
+ testcontainers-keycloak
+ ${testcontainers-keycloak.version}
+ test
+
+
+ org.testcontainers
+ testcontainers
+ ${testcontainers.version}
+
+
+ org.testcontainers
+ postgresql
+
+ test
+
org.llorllale
cactoos-matchers
@@ -199,17 +263,23 @@ SOFTWARE.
- org.apache.maven.plugins
- maven-checkstyle-plugin
- ${maven-checkstyle-plugin.version}
+ com.qulice
+ qulice-maven-plugin
+ ${qulice.version}
- checkstyle-suppressions.xml
+ file:${basedir}/LICENSE.txt
check
+
+
+ duplicatefinder:.*
+ dependencies:.*
+
+
diff --git a/realm-export.json b/realm-export.json
new file mode 100644
index 0000000..0c02c80
--- /dev/null
+++ b/realm-export.json
@@ -0,0 +1,2272 @@
+{
+ "id": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "realm": "pmo",
+ "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,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "roles": {
+ "realm": [
+ {
+ "id": "5b9a10ca-eb05-480b-b300-d6304d3c9383",
+ "name": "default-roles-pmo",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ },
+ {
+ "id": "0cefb977-1923-4ef2-9258-24f56b0c3e51",
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ },
+ {
+ "id": "79dad14b-4887-4002-a3f3-05dbd3c8d1bf",
+ "name": "user_github",
+ "description": "",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ },
+ {
+ "id": "afb79c93-03f6-404f-96c8-080c7803d2a1",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ },
+ {
+ "id": "3d894791-89cb-4c1b-af73-a49aea41adfb",
+ "name": "admin",
+ "description": "",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ },
+ {
+ "id": "1d64d76a-0441-497a-ba17-f6db68c4a493",
+ "name": "user",
+ "description": "",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec",
+ "attributes": {}
+ }
+ ],
+ "client": {
+ "realm-management": [
+ {
+ "id": "f90a6fd6-aef3-4239-82b8-f79603f58840",
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "46c1fa5a-a47d-4011-a6d4-e8221afe1d42",
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "cb5f713d-c252-45d0-a92c-060844ce865e",
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "3c167bf9-2c2f-4ec6-8168-08524e8e25cd",
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "4dca663e-6355-4793-812f-eaa4f763f15e",
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "f605df6d-b64a-4a16-8cb8-3ecdab582916",
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "e5bf5c55-aa6d-42e4-8a26-95caef9a561c",
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "52fa6d30-ae5f-489d-bf55-b0bf90d25772",
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "1ade7ba9-7585-4c19-aedd-c1a4f071d8df",
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "6e3f3ea8-b8ce-4c66-8114-3e23b50cdd9d",
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "4605fe63-ecaf-46f7-851e-f0a6dd7d884a",
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "5313bbc4-9078-49e4-9b0d-41031cfc06bd",
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "d6611b0c-236e-435c-94ca-3ca83e39ea54",
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "25be2ed6-2cd4-4d23-ab20-0ea10992e197",
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "e2b0a145-a236-4063-bcac-6da6edb2dcf4",
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "b9e0e231-c848-4114-8277-d8810308e1e1",
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "fb975dc8-d326-48df-b286-7276fc223d73",
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "5d31965e-8c83-47f2-a057-207f1d3f90ac",
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "view-events",
+ "query-clients",
+ "view-realm",
+ "manage-realm",
+ "impersonation",
+ "manage-clients",
+ "manage-identity-providers",
+ "manage-authorization",
+ "query-users",
+ "query-realms",
+ "create-client",
+ "manage-events",
+ "query-groups",
+ "view-clients",
+ "view-identity-providers",
+ "view-authorization",
+ "manage-users",
+ "view-users"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ },
+ {
+ "id": "c952ba76-ce6a-42a6-a52e-a0d76838f9d7",
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-users",
+ "query-groups"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "attributes": {}
+ }
+ ],
+ "security-admin-console": [],
+ "tracehub-pmo": [
+ {
+ "id": "8bb2087e-7f6a-4c97-bd16-9ac93c81d87f",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "8bc708b1-5b38-4a86-a9cc-af834e0d6784",
+ "attributes": {}
+ }
+ ],
+ "admin-cli": [],
+ "account-console": [],
+ "broker": [
+ {
+ "id": "ccdb1ae0-9cfa-4158-a90e-abc2bcf3cf24",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "90a4886f-3bf1-4e91-9adb-52e732093318",
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "id": "cca706ea-3b8a-4be8-a4e8-55d64fe06ae3",
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "10266cac-6232-41e6-97d8-98775c0be0c8",
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "04a6aad0-9c42-4e9c-9e75-f320f8351d72",
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "b3573f67-2238-46bb-b4cc-7bc2180eabdd",
+ "name": "view-groups",
+ "description": "${role_view-groups}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "88da235d-f0c9-4597-ac03-35716361e2b1",
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "6be93ea3-e654-4aa4-9847-32d73a5aec56",
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "4258490d-45e8-45cf-9d81-b56e51295b7d",
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "221c5a84-a8e8-4a20-9183-6625c63c63d2",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ },
+ {
+ "id": "897b05bd-1566-4567-9721-7510490d889b",
+ "name": "delete-account",
+ "description": "${role_delete-account}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "attributes": {}
+ }
+ ]
+ }
+ },
+ "groups": [],
+ "defaultRole": {
+ "id": "5b9a10ca-eb05-480b-b300-d6304d3c9383",
+ "name": "default-roles-pmo",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "458b384c-be7e-4622-b38c-739cc99fcdec"
+ },
+ "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": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account",
+ "view-groups"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "cc42ce98-43ff-495b-82b6-45e6c68c4114",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/pmo/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/pmo/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": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "f9dc08c6-e0a9-4b4b-a081-ec4a6e4c0f3d",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/pmo/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/pmo/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": "e9a49998-3b9c-433a-9c7a-a523a3d73227",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "57473aca-ee87-43e9-b1fa-5aa86db3389f",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "90a4886f-3bf1-4e91-9adb-52e732093318",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "250c78f1-29a1-4f2f-bb9c-9864b67f4aa3",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "5bf878ec-f1ee-4845-ac05-b29d413f45b1",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/pmo/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/pmo/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": "64bcc88f-2b20-4a10-b4b2-2f1fc8978a89",
+ "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": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "8bc708b1-5b38-4a86-a9cc-af834e0d6784",
+ "clientId": "tracehub-pmo",
+ "name": "",
+ "description": "",
+ "rootUrl": "",
+ "adminUrl": "",
+ "baseUrl": "",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "http://localhost:8080/*",
+ "*"
+ ],
+ "webOrigins": [
+ "http://localhost:8080"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": true,
+ "protocol": "openid-connect",
+ "attributes": {
+ "oidc.ciba.grant.enabled": "false",
+ "client.secret.creation.time": "1705571311",
+ "backchannel.logout.session.required": "true",
+ "oauth2.device.authorization.grant.enabled": "false",
+ "display.on.consent.screen": "false",
+ "backchannel.logout.revoke.offline.tokens": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "32bfb57e-e6f0-429d-871c-3170e3c6e22e",
+ "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": "6dc75b6c-6c78-4b8b-8326-ae40a87b5c64",
+ "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": "13c3d4ea-2263-4653-830b-f0d7dbd42150",
+ "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": "5bf6d09b-a651-4452-bccf-71e2ea5fb923",
+ "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": "fd0a58a7-dcb3-45f7-81d4-6b806492adfc",
+ "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": "7ff9efd8-b82a-40b3-9190-54e1c4d07a10",
+ "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": "b68c93e4-0296-4b91-9abc-ca64bc717f15",
+ "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": "6f77b84f-bb3d-4e98-a6fb-16d6d3b30abe",
+ "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": "ec5e90d5-a38f-4558-80a3-a013ae7e4f15",
+ "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": "7bd47744-b673-4224-b068-50473ec0edb0",
+ "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": "904169f7-a811-4c04-ac75-175c13f6b987",
+ "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": "ada8378c-20c7-478c-b0ae-99f1b11d8348",
+ "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": "ef82c9c1-d043-4f33-8994-83e7afafafe6",
+ "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": "d604cabc-dedb-4d63-9914-3779243fdc47",
+ "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": "e0f12c52-fc73-4c30-87b4-874b9080a6e8",
+ "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": "750a5109-7aba-474f-b99c-a583c5cb648d",
+ "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": "a2e22207-c845-4e9e-9742-e2593dcdec11",
+ "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": "13297a17-5fb0-48f2-a52e-053a40f36b30",
+ "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": "eff78f20-202a-4492-97e4-266f28797476",
+ "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": "3b21af7b-ba71-41e5-886f-275070e7d4a4",
+ "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": "8f29eecb-6b13-4616-b7b6-57030f7d9002",
+ "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": "b6654374-83e5-42ae-af34-455b59844e20",
+ "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": "045da296-b1d5-4f74-9294-edb641a8229c",
+ "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": "2bb52d0b-f243-4560-8c29-e2a96b38e3a1",
+ "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": "344cfced-5aa1-4478-b550-022f6a8eb15c",
+ "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": "9d1775a7-fcb0-4b70-8a81-536fff8f87f8",
+ "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": "e509ad1a-f77c-4c27-8819-fe56e6dd4c25",
+ "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": "82ea2bd5-57d2-4a75-8e8a-cb49b1359bee",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {
+ "introspection.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "1f398e90-1bb0-4e97-9be5-85b3cac9dbcd",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "6f805233-706a-42ca-80a9-5395079bbdb9",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "cb9859bb-b35e-447e-b7bc-5172b1d23770",
+ "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": "e6c7eee2-cd02-4ddb-85f4-27d7e37a15a8",
+ "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": "aeec04ea-cd83-4c6a-b09c-8415eef18a49",
+ "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": "fdd85168-3a56-49ca-b9f0-ec62180c084d",
+ "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": "dbbf324c-a2d1-4073-82da-29223a6ff885",
+ "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": "f7820f9b-e49c-4235-836f-71f9236c87c0",
+ "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": "923248e7-f5ad-4351-9ec1-99d02cb8083a",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {
+ "introspection.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "role_list",
+ "profile",
+ "email",
+ "web-origins",
+ "acr",
+ "roles"
+ ],
+ "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": [
+ {
+ "alias": "github",
+ "internalId": "746fa90e-c8ef-4f25-8185-d7eca220f434",
+ "providerId": "github",
+ "enabled": true,
+ "updateProfileFirstLoginMode": "on",
+ "trustEmail": true,
+ "storeToken": true,
+ "addReadTokenRoleOnCreate": false,
+ "authenticateByDefault": false,
+ "linkOnly": false,
+ "firstBrokerLoginFlowAlias": "first broker login",
+ "config": {
+ "hideOnLoginPage": "false",
+ "clientId": "499b6f4de7f3f64219ed",
+ "acceptsPromptNoneForwardFromClient": "false",
+ "disableUserInfo": "false",
+ "filteredByClaim": "false",
+ "syncMode": "IMPORT",
+ "clientSecret": "**********",
+ "defaultScope": "repo"
+ }
+ }
+ ],
+ "identityProviderMappers": [
+ {
+ "id": "065ac658-7dca-4bf1-95f8-586daf8f5ba5",
+ "name": "read-token",
+ "identityProviderAlias": "github",
+ "identityProviderMapper": "github-user-attribute-mapper",
+ "config": {
+ "syncMode": "INHERIT"
+ }
+ },
+ {
+ "id": "4025541b-ece8-463f-9d2c-6395f62ea837",
+ "name": "Github role",
+ "identityProviderAlias": "github",
+ "identityProviderMapper": "oidc-hardcoded-role-idp-mapper",
+ "config": {
+ "syncMode": "INHERIT",
+ "role": "user_github"
+ }
+ }
+ ],
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "id": "c60e5fc2-e975-47d4-b597-74c4a424375e",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "ca5836ad-0181-4b24-ad35-ad55afb444fb",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "1d796cfc-706b-4d5d-b8d1-a4689a976a6d",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "132c70fa-b3ec-4362-9661-9a6424637e85",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "4f91512d-7207-4b01-b31b-4b53e785f3c3",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "b94e7f77-d185-4e0d-9ead-59b84e674fd6",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "1399ee2a-7929-4c46-b87e-82dcfd01e4ea",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-attribute-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "saml-role-list-mapper",
+ "saml-user-property-mapper",
+ "oidc-full-name-mapper",
+ "oidc-address-mapper"
+ ]
+ }
+ },
+ {
+ "id": "2038f19e-18e9-4696-b130-f21edabda873",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "saml-user-attribute-mapper",
+ "saml-user-property-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "oidc-address-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-full-name-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "0b856887-6928-4777-89ab-fecdbee4b7e7",
+ "name": "rsa-enc-generated",
+ "providerId": "rsa-enc-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "RSA-OAEP"
+ ]
+ }
+ },
+ {
+ "id": "d48d0b96-c0bd-42a3-982b-afdb323d9ad5",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "b9079ccf-005d-4c9a-a14b-498583c45d50",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "a9f3704a-5a61-4d90-9be8-be2942f532b2",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "f53abfba-5d81-4641-8563-a90795eae7d5",
+ "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": "1ed377fe-0c40-4d27-bd9f-b79dcd3a4bd4",
+ "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": "a88c060e-2e79-4c93-af0e-74a8e250b9c8",
+ "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": "de7ded19-cf17-463b-9e85-5363d2871594",
+ "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": "1b14e5b3-7bf1-4afe-92bb-2fada2e87dc2",
+ "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": "9f7b59da-ec74-4325-8d8c-f443370d82ee",
+ "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": "b8781215-9271-4d6d-baa0-a9e536aaf525",
+ "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": "03767276-033f-49b5-9f7c-57b94325e197",
+ "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": "7e40665b-86bf-425a-abd7-46127eb20f05",
+ "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": "1059bc15-b32b-413e-b835-c0cd348b92ec",
+ "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": "dcad1939-297c-425f-82c0-73e9d2d8dfc3",
+ "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": "dd0230fd-734b-4141-81f2-8a0d073c13f2",
+ "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": "1223409f-1b9f-476e-aaf8-4a6fd54e2e0d",
+ "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": "dbdacd7c-f79e-42d1-9b9f-b4c35412e3da",
+ "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": "9d70d2e2-743c-4658-99a2-c8b74bd3515d",
+ "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": "aef73528-60ea-4e53-b3d0-a8bfad91c6c1",
+ "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
+ }
+ ]
+ },
+ {
+ "id": "ca94673c-19be-45ca-ba11-5ae42c396991",
+ "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": "ea260f4e-4ca4-4e7f-8d34-c54124297ac8",
+ "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": "3fd643b2-123d-4924-a5e0-be9e92d4a6e0",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "fe723fe5-a6ab-45c0-b692-d7274a820183",
+ "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": "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",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "oauth2DevicePollingInterval": "5",
+ "parRequestUriLifespan": "60",
+ "cibaInterval": "5",
+ "realmReusableOtpCode": "false"
+ },
+ "keycloakVersion": "23.0.4",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/git/tracehub/pmo/PmoApplication.java b/src/main/java/git/tracehub/pmo/PmoApplication.java
index b139d15..d920235 100644
--- a/src/main/java/git/tracehub/pmo/PmoApplication.java
+++ b/src/main/java/git/tracehub/pmo/PmoApplication.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023-2024 Tracehub
+ * Copyright (c) 2023-2024 Tracehub.git
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to read
@@ -23,18 +23,21 @@
/**
* Entry point.
*
+ * @checkstyle HideUtilityClassConstructorCheck (10 lines)
* @since 0.0.0
*/
@SpringBootApplication
+@SuppressWarnings("PMD.UseUtilityClass")
public class PmoApplication {
- /**
- * Application entry point.
- *
- * @param args Application arguments
- */
- public static void main(final String[] args) {
- SpringApplication.run(PmoApplication.class, args);
- }
+ /**
+ * Application entry point.
+ *
+ * @param args Application arguments
+ */
+ @SuppressWarnings("ProhibitPublicStaticMethods")
+ public static void main(final String[] args) {
+ SpringApplication.run(PmoApplication.class, args);
+ }
}
diff --git a/src/main/java/git/tracehub/pmo/controller/ProjectController.java b/src/main/java/git/tracehub/pmo/controller/ProjectController.java
new file mode 100644
index 0000000..ee7d4f4
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/controller/ProjectController.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.controller;
+
+import git.tracehub.pmo.platforms.github.InviteCollaborator;
+import git.tracehub.pmo.project.Project;
+import git.tracehub.pmo.project.Projects;
+import git.tracehub.pmo.security.ClaimOf;
+import git.tracehub.pmo.security.ExistsRole;
+import git.tracehub.pmo.security.IdpToken;
+import java.util.List;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Project Controller.
+ *
+ * @since 0.0.0
+ */
+@RestController
+@RequiredArgsConstructor
+public class ProjectController {
+
+ /**
+ * Projects.
+ */
+ private final Projects projects;
+
+ /**
+ * Issuer url.
+ */
+ @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
+ private String url;
+
+ /**
+ * Projects by user.
+ *
+ * @param jwt Jwt
+ * @return List of projects
+ */
+ @GetMapping
+ public List byUser(@AuthenticationPrincipal final Jwt jwt) {
+ return this.projects.byUser(
+ new ClaimOf(jwt, "preferred_username").value()
+ );
+ }
+
+ /**
+ * Project by id.
+ *
+ * @param id Project id
+ * @return Project
+ */
+ @GetMapping("/{id}")
+ /*
+ * @todo #1:45min/DEV check if authenticated user can access the Project.
+ * we need create security checks that will made a statement
+ * does authenticated user can access the project or not:
+ * if project is public, every one can see it;
+ * otherwise user must be individual performer or team member.
+ */
+ public Project byId(@PathVariable final UUID id) {
+ return this.projects.byId(id);
+ }
+
+ /**
+ * Employ new project.
+ *
+ * @param project Project
+ * @param jwt Jwt
+ * @return Project
+ * @checkstyle MethodBodyCommentsCheck (20 lines)
+ */
+ @PostMapping
+ /*
+ * @todo #1:45min/DEV check if authenticated user can create a new
+ * project. We need to create security checks that will make a statement
+ * does authenticated user can create a new project or not:
+ * if project is public, every one can create it;
+ * otherwise we need to request a payment from the user.
+ */
+ @ResponseStatus(HttpStatus.CREATED)
+ public Project employ(
+ @RequestBody final Project project,
+ @AuthenticationPrincipal final Jwt jwt
+ ) {
+ final Project created = this.projects.employ(project);
+ /*
+ * @todo #1:45min/DEV define appropriate agent according to location
+ * of the project. We need to define appropriate agent and call
+ * corresponding implementation to invite collaborators here.
+ */
+ if (new ExistsRole(jwt, "user_github").value()) {
+ new InviteCollaborator(
+ created.getLocation(),
+ "tracehubgit",
+ new IdpToken(jwt, "github", this.url).value()
+ ).exec();
+ }
+ return created;
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/controller/package-info.java b/src/main/java/git/tracehub/pmo/controller/package-info.java
new file mode 100644
index 0000000..aceb641
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/controller/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Controllers.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.controller;
diff --git a/src/main/java/git/tracehub/pmo/exception/ResourceNotFoundException.java b/src/main/java/git/tracehub/pmo/exception/ResourceNotFoundException.java
new file mode 100644
index 0000000..eb86a43
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/exception/ResourceNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.exception;
+
+/**
+ * ResourceNotFoundException.
+ *
+ * @since 0.0.0
+ */
+public class ResourceNotFoundException extends RuntimeException {
+
+ /**
+ * Constructor.
+ *
+ * @param message Message
+ */
+ public ResourceNotFoundException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/git/tracehub/pmo/exception/package-info.java b/src/main/java/git/tracehub/pmo/exception/package-info.java
new file mode 100644
index 0000000..528b87a
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/exception/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Exceptions.
+ */
+package git.tracehub.pmo.exception;
diff --git a/src/main/java/git/tracehub/pmo/package-info.java b/src/main/java/git/tracehub/pmo/package-info.java
index 8d6e417..f99a595 100644
--- a/src/main/java/git/tracehub/pmo/package-info.java
+++ b/src/main/java/git/tracehub/pmo/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023-2024 Tracehub
+ * Copyright (c) 2023-2024 Tracehub.git
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to read
diff --git a/src/main/java/git/tracehub/pmo/platforms/Action.java b/src/main/java/git/tracehub/pmo/platforms/Action.java
new file mode 100644
index 0000000..2eab401
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/platforms/Action.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.platforms;
+
+/**
+ * Action.
+ *
+ * @since 0.0.0
+ */
+public interface Action {
+
+ /**
+ * Execute.
+ */
+ void exec();
+
+}
diff --git a/src/main/java/git/tracehub/pmo/platforms/RepoPath.java b/src/main/java/git/tracehub/pmo/platforms/RepoPath.java
new file mode 100644
index 0000000..4971c5a
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/platforms/RepoPath.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.platforms;
+
+import lombok.RequiredArgsConstructor;
+import org.cactoos.Scalar;
+
+/**
+ * Extract path from provided location (ex. github@user/repo:branch).
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class RepoPath implements Scalar {
+
+ /**
+ * Location.
+ */
+ private final String location;
+
+ @Override
+ public String value() {
+ return this.location.replaceAll(
+ "([^@/]+)@([^/]+)/([^:]+):.*", "$2/$3"
+ );
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/platforms/github/InviteCollaborator.java b/src/main/java/git/tracehub/pmo/platforms/github/InviteCollaborator.java
new file mode 100644
index 0000000..4ada9c9
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/platforms/github/InviteCollaborator.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.platforms.github;
+
+import com.jcabi.github.Coordinates;
+import com.jcabi.github.RtGithub;
+import git.tracehub.pmo.platforms.Action;
+import git.tracehub.pmo.platforms.RepoPath;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+
+/**
+ * Invite collaborator.
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class InviteCollaborator implements Action {
+
+ /**
+ * Owner and repo.
+ */
+ private final String location;
+
+ /**
+ * Username.
+ */
+ private final String username;
+
+ /**
+ * Token.
+ */
+ private final String token;
+
+ @Override
+ @SneakyThrows
+ public void exec() {
+ new RtGithub(this.token).repos()
+ .get(
+ new Coordinates.Simple(
+ new RepoPath(this.location).value()
+ )
+ ).collaborators()
+ .add(this.username);
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/platforms/github/package-info.java b/src/main/java/git/tracehub/pmo/platforms/github/package-info.java
new file mode 100644
index 0000000..e7c9aa7
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/platforms/github/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Github.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.platforms.github;
diff --git a/src/main/java/git/tracehub/pmo/platforms/package-info.java b/src/main/java/git/tracehub/pmo/platforms/package-info.java
new file mode 100644
index 0000000..02c016d
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/platforms/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Platforms.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.platforms;
diff --git a/src/main/java/git/tracehub/pmo/project/AdjustedText.java b/src/main/java/git/tracehub/pmo/project/AdjustedText.java
new file mode 100644
index 0000000..40dee17
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/AdjustedText.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.util.function.Predicate;
+import lombok.AllArgsConstructor;
+import org.cactoos.Scalar;
+
+/**
+ * Adjusted text.
+ *
+ * @since 0.0.0
+ */
+@AllArgsConstructor
+public final class AdjustedText implements Scalar {
+
+ /**
+ * Raw text.
+ */
+ private final String text;
+
+ /**
+ * Condition.
+ */
+ private final Predicate condition;
+
+ @Override
+ public String value() {
+ String value = "";
+ if (this.condition.test(this.text)) {
+ value = this.text;
+ }
+ return value;
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/project/DateOf.java b/src/main/java/git/tracehub/pmo/project/DateOf.java
new file mode 100644
index 0000000..81d07ed
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/DateOf.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.time.LocalDate;
+import lombok.AllArgsConstructor;
+import org.cactoos.Scalar;
+
+/**
+ * Date of.
+ *
+ * @since 0.0.0
+ */
+@AllArgsConstructor
+public final class DateOf implements Scalar {
+
+ /**
+ * Raw date.
+ */
+ private final String date;
+
+ @Override
+ public LocalDate value() {
+ LocalDate value = null;
+ if (this.date != null) {
+ value = LocalDate.parse(this.date);
+ }
+ return value;
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/project/DefaultProjects.java b/src/main/java/git/tracehub/pmo/project/DefaultProjects.java
new file mode 100644
index 0000000..8a6000c
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/DefaultProjects.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import com.jcabi.jdbc.JdbcSession;
+import git.tracehub.pmo.exception.ResourceNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import javax.sql.DataSource;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+
+/**
+ * Default implementation of projects.
+ *
+ * @checkstyle DesignForExtensionCheck (70 lines)
+ * @since 0.0.0
+ */
+@Component
+@RequiredArgsConstructor
+public class DefaultProjects implements Projects {
+
+ /**
+ * Datasource.
+ */
+ private final DataSource source;
+
+ @Override
+ @SneakyThrows
+ public Project byId(final UUID id) {
+ return new JdbcSession(this.source)
+ .sql(
+ new SqlStatement("select-project-by-id.sql").asString()
+ ).set(id)
+ .select(
+ (rs, stmt) -> {
+ if (!rs.next()) {
+ throw new ResourceNotFoundException(
+ String.format(
+ "Project %s not found",
+ id
+ )
+ );
+ }
+ return new ProjectOf(rs).value();
+ }
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public List byUser(final String email) {
+ return new JdbcSession(this.source)
+ .sql(
+ new SqlStatement("select-projects-by-user-email.sql")
+ .asString()
+ ).set(email)
+ .select(
+ (rs, stmt) -> {
+ final List projects = new ArrayList<>(5);
+ while (rs.next()) {
+ projects.add(new ProjectOf(rs).value());
+ }
+ return projects;
+ }
+ );
+ }
+
+ @Override
+ @SneakyThrows
+ public Project employ(final Project project) {
+ return new JdbcSession(this.source)
+ .sql(
+ new SqlStatement("insert-project.sql").asString()
+ ).set(project.getName())
+ .set(project.getLocation())
+ .set(project.getDescription())
+ .set(project.isActive())
+ .update(
+ (rs, stmt) -> {
+ rs.next();
+ return new ProjectOf(rs).value();
+ }
+ );
+ }
+}
diff --git a/src/main/java/git/tracehub/pmo/project/Project.java b/src/main/java/git/tracehub/pmo/project/Project.java
new file mode 100644
index 0000000..be25008
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/Project.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Project.
+ *
+ * @since 0.0.0
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Project {
+
+ /**
+ * Id.
+ */
+ private UUID id;
+
+ /**
+ * Name.
+ */
+ private String name;
+
+ /**
+ * Location.
+ */
+ private String location;
+
+ /**
+ * Description.
+ */
+ private String description;
+
+ /**
+ * Visibility.
+ */
+ private boolean active;
+
+}
diff --git a/src/main/java/git/tracehub/pmo/project/ProjectOf.java b/src/main/java/git/tracehub/pmo/project/ProjectOf.java
new file mode 100644
index 0000000..d6e44b2
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/ProjectOf.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.sql.ResultSet;
+import java.util.Objects;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.cactoos.Scalar;
+
+/**
+ * Project from result set.
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class ProjectOf implements Scalar {
+
+ /**
+ * Result set.
+ */
+ private final ResultSet set;
+
+ @Override
+ @SneakyThrows
+ public Project value() {
+ return new Project(
+ UUID.fromString(this.set.getString("id")),
+ this.set.getString("name"),
+ this.set.getString("location"),
+ new AdjustedText(
+ this.set.getString("description"),
+ Objects::nonNull
+ ).value(),
+ this.set.getBoolean("active")
+ );
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/project/Projects.java b/src/main/java/git/tracehub/pmo/project/Projects.java
new file mode 100644
index 0000000..8df4392
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/Projects.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Projects.
+ *
+ * @since 0.0.0
+ */
+public interface Projects {
+
+ /**
+ * Project by id.
+ *
+ * @param id Project ID
+ * @return Project
+ */
+ Project byId(UUID id);
+
+ /**
+ * Projects by user email.
+ *
+ * @param email User Email
+ * @return List of projects
+ */
+ List byUser(String email);
+
+ /**
+ * Employ new project.
+ *
+ * @param project Project to Employ
+ * @return Project
+ */
+ Project employ(Project project);
+}
diff --git a/src/main/java/git/tracehub/pmo/project/Sql.java b/src/main/java/git/tracehub/pmo/project/Sql.java
new file mode 100644
index 0000000..d8ae9ef
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/Sql.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import org.cactoos.Text;
+
+/**
+ * SQL.
+ *
+ * @since 0.0.0
+ */
+public interface Sql extends Text {
+}
diff --git a/src/main/java/git/tracehub/pmo/project/SqlStatement.java b/src/main/java/git/tracehub/pmo/project/SqlStatement.java
new file mode 100644
index 0000000..49b5d47
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/SqlStatement.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.io.InputStream;
+import org.cactoos.io.ResourceOf;
+import org.cactoos.text.TextOf;
+
+/**
+ * SQL Statement.
+ *
+ * @since 0.0.0
+ */
+public final class SqlStatement implements Sql {
+
+ /**
+ * InputStream.
+ */
+ private final InputStream input;
+
+ /**
+ * Ctor.
+ *
+ * @param path Path
+ * @throws Exception if something went wrong
+ */
+ public SqlStatement(final String path) throws Exception {
+ this(new ResourceOf("sql/%s".formatted(path)).stream());
+ }
+
+ /**
+ * Ctor.
+ *
+ * @param stream InputStream
+ */
+ public SqlStatement(final InputStream stream) {
+ this.input = stream;
+ }
+
+ @Override
+ public String asString() throws Exception {
+ return new TextOf(
+ this.input
+ ).asString();
+ }
+}
diff --git a/src/main/java/git/tracehub/pmo/project/package-info.java b/src/main/java/git/tracehub/pmo/project/package-info.java
new file mode 100644
index 0000000..e77a101
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/project/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Projects.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.project;
diff --git a/src/main/java/git/tracehub/pmo/security/ClaimOf.java b/src/main/java/git/tracehub/pmo/security/ClaimOf.java
new file mode 100644
index 0000000..5f04307
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/security/ClaimOf.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import lombok.RequiredArgsConstructor;
+import org.cactoos.Scalar;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Claim from provided token.
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class ClaimOf implements Scalar {
+
+ /**
+ * JWT.
+ */
+ private final Jwt jwt;
+
+ /**
+ * Claim.
+ */
+ private final String claim;
+
+ @Override
+ public String value() {
+ return this.jwt.getClaim(this.claim);
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/security/ExistsRole.java b/src/main/java/git/tracehub/pmo/security/ExistsRole.java
new file mode 100644
index 0000000..b962d8b
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/security/ExistsRole.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.cactoos.Scalar;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Does the user have provided role from token.
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class ExistsRole implements Scalar {
+
+ /**
+ * JWT.
+ */
+ private final Jwt jwt;
+
+ /**
+ * Role.
+ */
+ private final String role;
+
+ @Override
+ public Boolean value() {
+ final Map map = this.jwt.getClaimAsMap("realm_access");
+ return ((List) map.get("roles")).contains(this.role);
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/security/IdpToken.java b/src/main/java/git/tracehub/pmo/security/IdpToken.java
new file mode 100644
index 0000000..d31a528
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/security/IdpToken.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import com.jcabi.http.Request;
+import com.jcabi.http.request.JdkRequest;
+import com.jcabi.http.response.RestResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.cactoos.Scalar;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Token from IDP.
+ *
+ * @checkstyle MethodBodyCommentsCheck (50 lines)
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+public final class IdpToken implements Scalar {
+
+ /**
+ * JWT.
+ */
+ private final Jwt jwt;
+
+ /**
+ * Provider name.
+ */
+ private final String provider;
+
+ /**
+ * Base url.
+ */
+ private final String url;
+
+ @Override
+ @SneakyThrows
+ public String value() {
+ /*
+ * @todo #1:45min/DEV fix 403 Forbidden error when trying to get
+ * token from IDP. It seems that the user hasn't enough permissions
+ * to get the token from IDP. We need to configure Keycloak to allow
+ * the user to read the token. See the following link for more info:
+ * https://www.keycloak.org/docs/latest/server_admin/#retrieving-external-idp-tokens
+ */
+ new JdkRequest(
+ "%s//broker/%s/token".formatted(
+ this.url,
+ this.provider
+ )
+ ).method(Request.GET)
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
+ .header(
+ HttpHeaders.AUTHORIZATION,
+ "Bearer %s".formatted(this.jwt.getTokenValue())
+ ).fetch()
+ .as(RestResponse.class);
+ return null;
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/security/SecurityConfig.java b/src/main/java/git/tracehub/pmo/security/SecurityConfig.java
new file mode 100644
index 0000000..b2a17c3
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/security/SecurityConfig.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import lombok.SneakyThrows;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * Security configurations.
+ *
+ * @since 0.0.0
+ */
+@Configuration
+@EnableMethodSecurity
+public class SecurityConfig {
+
+ /**
+ * Filter.
+ *
+ * @param http HttpSecurity
+ * @return SecurityFilterChain
+ * @checkstyle NonStaticMethodCheck (30 lines)
+ */
+ @Bean
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ @SneakyThrows
+ public SecurityFilterChain client(final HttpSecurity http) {
+ return http.cors(Customizer.withDefaults())
+ .csrf(AbstractHttpConfigurer::disable)
+ .httpBasic(AbstractHttpConfigurer::disable)
+ .sessionManagement(
+ configurer ->
+ configurer.sessionCreationPolicy(
+ SessionCreationPolicy.STATELESS
+ )
+ ).authorizeHttpRequests(
+ auth -> auth
+ .requestMatchers("/login").permitAll()
+ .anyRequest().authenticated()
+ ).exceptionHandling(
+ configurer -> configurer
+ .accessDeniedHandler(
+ (request, response, ex) -> {
+ response.setStatus(HttpStatus.FORBIDDEN.value());
+ response.getWriter().write("Access denied");
+ }
+ )
+ ).oauth2ResourceServer(
+ configurer -> configurer.jwt(Customizer.withDefaults())
+ ).build();
+ }
+
+}
diff --git a/src/main/java/git/tracehub/pmo/security/package-info.java b/src/main/java/git/tracehub/pmo/security/package-info.java
new file mode 100644
index 0000000..bc6461d
--- /dev/null
+++ b/src/main/java/git/tracehub/pmo/security/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Security.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.security;
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index 73b9f66..d1b3abf 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -1,3 +1,19 @@
server:
- port: 80
+ port: 8080
shutdown: graceful
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: ${ISSUER_URI}
+ liquibase:
+ user: ${POSTGRES_USER}
+ password: ${POSTGRES_PASSWORD}
+ driver-class-name: org.postgresql.Driver
+ url: jdbc:postgresql://${POSTGRES_HOST}:5432/postgres
+ datasource:
+ username: ${POSTGRES_USER}
+ password: ${POSTGRES_PASSWORD}
+ driver-class-name: org.postgresql.Driver
+ url: jdbc:postgresql://${POSTGRES_HOST}:5432/postgres
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/2024/001-projects.sql b/src/main/resources/db/changelog/2024/001-projects.sql
new file mode 100644
index 0000000..b884fee
--- /dev/null
+++ b/src/main/resources/db/changelog/2024/001-projects.sql
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+--liquibase formatted sql
+
+--changeset hizmailovich:1
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+--changeset hizmailovich:2
+CREATE SCHEMA IF NOT EXISTS projects;
+
+--changeset hizmailovich:3
+CREATE TABLE IF NOT EXISTS projects.projects
+(
+ id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
+ name CHARACTER VARYING(64) NOT NULL,
+ location CHARACTER VARYING(64) UNIQUE NOT NULL,
+ description CHARACTER VARYING(256),
+ active BOOLEAN NOT NULL
+);
+
+--changeset hizmailovich:4
+CREATE TABLE IF NOT EXISTS projects.performers
+(
+ id uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
+ email CHARACTER VARYING(64) NOT NULL,
+ project uuid NOT NULL REFERENCES projects.projects,
+ permission CHARACTER VARYING(16) NOT NULL
+);
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml
new file mode 100644
index 0000000..2bda9da
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -0,0 +1,3 @@
+databaseChangeLog:
+ - includeAll:
+ path: db/changelog/2024
diff --git a/src/main/resources/sql/insert-project.sql b/src/main/resources/sql/insert-project.sql
new file mode 100644
index 0000000..6b681eb
--- /dev/null
+++ b/src/main/resources/sql/insert-project.sql
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+INSERT INTO projects.projects (name,
+ location,
+ description,
+ active)
+VALUES (?, ?, ?, ?)
+RETURNING id,
+ name,
+ location,
+ description,
+ active;
\ No newline at end of file
diff --git a/src/main/resources/sql/select-project-by-id.sql b/src/main/resources/sql/select-project-by-id.sql
new file mode 100644
index 0000000..fcd473c
--- /dev/null
+++ b/src/main/resources/sql/select-project-by-id.sql
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+SELECT id,
+ name,
+ location,
+ description,
+ active
+FROM projects.projects
+WHERE id = ?;
diff --git a/src/main/resources/sql/select-projects-by-user-email.sql b/src/main/resources/sql/select-projects-by-user-email.sql
new file mode 100644
index 0000000..1ac4636
--- /dev/null
+++ b/src/main/resources/sql/select-projects-by-user-email.sql
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+SELECT p.id,
+ p.name,
+ p.location,
+ p.description,
+ p.active
+FROM projects.projects p
+ INNER JOIN projects.performers on p.id = performers.project
+WHERE performers.email = ?;
diff --git a/src/test/java/git/tracehub/pmo/package-info.java b/src/test/java/git/tracehub/pmo/package-info.java
new file mode 100644
index 0000000..4b57ef5
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Pmo Tests.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo;
diff --git a/src/test/java/git/tracehub/pmo/platforms/RepoPathTest.java b/src/test/java/git/tracehub/pmo/platforms/RepoPathTest.java
new file mode 100644
index 0000000..964844a
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/platforms/RepoPathTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.platforms;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test suite for {@link RepoPath}.
+ *
+ * @since 0.0.0
+ */
+final class RepoPathTest {
+
+ @Test
+ void returnsOwnerAndRepoFromLocation() {
+ final String path = new RepoPath("github@user/repo:branch").value();
+ MatcherAssert.assertThat(
+ "Path %s isn't correct".formatted(path),
+ path,
+ new IsEqual<>("user/repo")
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/platforms/package-info.java b/src/test/java/git/tracehub/pmo/platforms/package-info.java
new file mode 100644
index 0000000..884c405
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/platforms/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Platforms Tests.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.platforms;
diff --git a/src/test/java/git/tracehub/pmo/project/AdjustedTextTest.java b/src/test/java/git/tracehub/pmo/project/AdjustedTextTest.java
new file mode 100644
index 0000000..bb37272
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/project/AdjustedTextTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test suite for {@link AdjustedText}.
+ *
+ * @since 0.0.0
+ */
+final class AdjustedTextTest {
+
+ @Test
+ void returnsEmptyStringWhenConditionIsFalse() {
+ final String text = new AdjustedText("Raw", condition -> false)
+ .value();
+ MatcherAssert.assertThat(
+ "String %s is not empty".formatted(text),
+ text,
+ new IsEqual<>("")
+ );
+ }
+
+ @Test
+ void returnsRawStringWhenConditionIsTrue() {
+ final String text = new AdjustedText("Raw", condition -> true)
+ .value();
+ MatcherAssert.assertThat(
+ "String %s is empty".formatted(text),
+ text,
+ new IsEqual<>("Raw")
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/project/DateOfTest.java b/src/test/java/git/tracehub/pmo/project/DateOfTest.java
new file mode 100644
index 0000000..f66670f
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/project/DateOfTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.time.LocalDate;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test suite for {@link DateOf}.
+ *
+ * @since 0.0.0
+ */
+final class DateOfTest {
+
+ @Test
+ void returnsNullWhenStringIsEmpty() {
+ final LocalDate date = new DateOf(null).value();
+ MatcherAssert.assertThat(
+ "Date %s is not empty".formatted(date),
+ date,
+ new IsEqual<>(null)
+ );
+ }
+
+ @Test
+ void returnsDateWhenStringExists() {
+ final LocalDate date = new DateOf(
+ LocalDate.of(2024, 1, 17).toString()
+ ).value();
+ MatcherAssert.assertThat(
+ "Date %s is empty".formatted(date),
+ date,
+ new IsEqual<>(LocalDate.of(2024, 1, 17))
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/project/ProjectOfTest.java b/src/test/java/git/tracehub/pmo/project/ProjectOfTest.java
new file mode 100644
index 0000000..ba5612e
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/project/ProjectOfTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.UUID;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Test suite for {@link ProjectOf}.
+ *
+ * @since 0.0.0
+ */
+@ExtendWith(MockitoExtension.class)
+final class ProjectOfTest {
+
+ /**
+ * Result set.
+ */
+ @Mock
+ private ResultSet set;
+
+ @Test
+ void returnsProjectWithAllFields() throws SQLException {
+ final Project expected = new Project(
+ UUID.randomUUID(),
+ "Test Project",
+ "Location",
+ "Description",
+ true
+ );
+ Mockito.when(this.set.getString("id"))
+ .thenReturn(expected.getId().toString());
+ Mockito.when(this.set.getString("name"))
+ .thenReturn(expected.getName());
+ Mockito.when(this.set.getString("location"))
+ .thenReturn(expected.getLocation());
+ Mockito.when(this.set.getString("description"))
+ .thenReturn(expected.getDescription());
+ Mockito.when(this.set.getBoolean("active"))
+ .thenReturn(expected.isActive());
+ final Project project = new ProjectOf(this.set).value();
+ MatcherAssert.assertThat(
+ "Project %s is null".formatted(project),
+ project,
+ Matchers.notNullValue()
+ );
+ MatcherAssert.assertThat(
+ "Project %s isn't correct".formatted(project),
+ project,
+ new IsEqual<>(expected)
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/project/SqlStatementTest.java b/src/test/java/git/tracehub/pmo/project/SqlStatementTest.java
new file mode 100644
index 0000000..ce97d04
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/project/SqlStatementTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.project;
+
+import org.cactoos.io.ResourceOf;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test suite for {@link SqlStatement}.
+ *
+ * @since 0.0.0
+ */
+final class SqlStatementTest {
+
+ @Test
+ void readsSqlInRightFormat() throws Exception {
+ MatcherAssert.assertThat(
+ "SQL is not in right format",
+ new SqlStatement(
+ new ResourceOf("analyze.sql").stream()
+ ).asString(),
+ new IsEqual<>("ANALYZE my_table")
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/project/package-info.java b/src/test/java/git/tracehub/pmo/project/package-info.java
new file mode 100644
index 0000000..7900f76
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/project/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Project Tests.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.project;
diff --git a/src/test/java/git/tracehub/pmo/security/ClaimOfTest.java b/src/test/java/git/tracehub/pmo/security/ClaimOfTest.java
new file mode 100644
index 0000000..f9ed311
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/security/ClaimOfTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.hamcrest.core.IsNull;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Test suite for {@link ClaimOf}.
+ *
+ * @since 0.0.0
+ */
+@ExtendWith(MockitoExtension.class)
+final class ClaimOfTest {
+
+ /**
+ * JWT.
+ */
+ @Mock
+ private Jwt jwt;
+
+ @Test
+ void returnsValueWhenClaimExists() {
+ final String value = "value";
+ Mockito.when(this.jwt.getClaim("claim")).thenReturn(value);
+ final String claim = new ClaimOf(this.jwt, "claim").value();
+ MatcherAssert.assertThat(
+ "Claim value %s is not %s".formatted(claim, value),
+ claim,
+ new IsEqual<>("value")
+ );
+ }
+
+ @Test
+ void returnsValueWhenClaimDoesNotExist() {
+ Mockito.when(this.jwt.getClaim("claim")).thenReturn(null);
+ final String claim = new ClaimOf(this.jwt, "claim").value();
+ MatcherAssert.assertThat(
+ "Claim value %s is not null".formatted(claim),
+ claim,
+ new IsNull<>()
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/security/ExistsRoleTest.java b/src/test/java/git/tracehub/pmo/security/ExistsRoleTest.java
new file mode 100644
index 0000000..9bec336
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/security/ExistsRoleTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package git.tracehub.pmo.security;
+
+import org.cactoos.list.ListOf;
+import org.cactoos.map.MapEntry;
+import org.cactoos.map.MapOf;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+/**
+ * Test suite for {@link ExistsRole}.
+ *
+ * @since 0.0.0
+ */
+@ExtendWith(MockitoExtension.class)
+final class ExistsRoleTest {
+
+ /**
+ * JWT.
+ */
+ @Mock
+ private Jwt jwt;
+
+ @Test
+ void returnsTrueIfRoleExists() {
+ Mockito.when(this.jwt.getClaimAsMap("realm_access")).thenReturn(
+ new MapOf<>(
+ new MapEntry<>("roles", new ListOf<>("role"))
+ )
+ );
+ MatcherAssert.assertThat(
+ "Role doesn't exist",
+ new ExistsRole(this.jwt, "role").value(),
+ new IsEqual<>(true)
+ );
+ }
+
+ @Test
+ void returnsFalseIfRoleDoesNotExist() {
+ Mockito.when(this.jwt.getClaimAsMap("realm_access")).thenReturn(
+ new MapOf<>(
+ new MapEntry<>("roles", new ListOf<>())
+ )
+ );
+ MatcherAssert.assertThat(
+ "Role exists",
+ new ExistsRole(this.jwt, "role").value(),
+ new IsEqual<>(false)
+ );
+ }
+
+}
diff --git a/src/test/java/git/tracehub/pmo/security/package-info.java b/src/test/java/git/tracehub/pmo/security/package-info.java
new file mode 100644
index 0000000..362dece
--- /dev/null
+++ b/src/test/java/git/tracehub/pmo/security/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Security Tests.
+ *
+ * @since 0.0.0
+ */
+package git.tracehub.pmo.security;
diff --git a/src/test/java/it/KeycloakIntegration.java b/src/test/java/it/KeycloakIntegration.java
new file mode 100644
index 0000000..3d627e3
--- /dev/null
+++ b/src/test/java/it/KeycloakIntegration.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package it;
+
+import dasniko.testcontainers.keycloak.KeycloakContainer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+/**
+ * Keycloak Integration Suite.
+ *
+ * @since 0.0.0
+ */
+@DirtiesContext
+@SuppressWarnings({
+ "JTCOP.RuleCorrectTestName",
+ "JTCOP.RuleAllTestsHaveProductionClass",
+ "PMD.ProhibitPublicStaticMethods"
+})
+public interface KeycloakIntegration {
+
+ /**
+ * Keycloak Container.
+ */
+ KeycloakContainer KEYCLOAK = new KeycloakContainer()
+ .withRealmImportFile("data/realm.json");
+
+ /**
+ * Properties.
+ *
+ * @param reg DynamicPropertyRegistry
+ */
+ @DynamicPropertySource
+ static void properties(final DynamicPropertyRegistry reg) {
+ reg.add(
+ "spring.security.oauth2.resourceserver.jwt.issuer-uri",
+ () -> "%s/realms/test".formatted(KeycloakIntegration.KEYCLOAK.getAuthServerUrl())
+ );
+ }
+
+ /**
+ * Start container.
+ */
+ @BeforeAll
+ static void start() {
+ KeycloakIntegration.KEYCLOAK.start();
+ }
+
+ /**
+ * Stop container.
+ */
+ @AfterAll
+ static void stop() {
+ KeycloakIntegration.KEYCLOAK.stop();
+ }
+}
diff --git a/src/test/java/it/PostgresIntegration.java b/src/test/java/it/PostgresIntegration.java
new file mode 100644
index 0000000..adf9c8f
--- /dev/null
+++ b/src/test/java/it/PostgresIntegration.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package it;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * PostgreSQL Integration Suite.
+ *
+ * @since 0.0.0
+ */
+@DirtiesContext
+@SuppressWarnings({
+ "JTCOP.RuleCorrectTestName",
+ "JTCOP.RuleAllTestsHaveProductionClass",
+ "PMD.ProhibitPublicStaticMethods"
+})
+public interface PostgresIntegration {
+
+ /**
+ * PostgreSQL Container.
+ */
+ PostgreSQLContainer> PG = new PostgreSQLContainer<>(
+ DockerImageName.parse("postgres:16.0-bullseye")
+ );
+
+ /**
+ * Properties.
+ *
+ * @param reg DynamicPropertyRegistry
+ */
+ @DynamicPropertySource
+ static void properties(final DynamicPropertyRegistry reg) {
+ reg.add("spring.datasource.url", PostgresIntegration.PG::getJdbcUrl);
+ reg.add("spring.liquibase.url", PostgresIntegration.PG::getJdbcUrl);
+ }
+
+ /**
+ * Start container.
+ */
+ @BeforeAll
+ static void start() {
+ PostgresIntegration.PG.start();
+ }
+
+ /**
+ * Stop container.
+ */
+ @AfterAll
+ static void stop() {
+ PostgresIntegration.PG.stop();
+ }
+}
diff --git a/src/test/java/it/package-info.java b/src/test/java/it/package-info.java
new file mode 100644
index 0000000..451becb
--- /dev/null
+++ b/src/test/java/it/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Integration tests.
+ *
+ * @since 0.0.0
+ */
+package it;
diff --git a/src/test/java/it/web/KeycloakToken.java b/src/test/java/it/web/KeycloakToken.java
new file mode 100644
index 0000000..e812534
--- /dev/null
+++ b/src/test/java/it/web/KeycloakToken.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package it.web;
+
+import com.jcabi.http.Request;
+import com.jcabi.http.request.JdkRequest;
+import com.jcabi.http.response.RestResponse;
+import io.github.eocqrs.eokson.Jocument;
+import io.github.eocqrs.eokson.JsonOf;
+import java.net.HttpURLConnection;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.cactoos.Scalar;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+
+/**
+ * Access token from Keycloak.
+ *
+ * @since 0.0.0
+ */
+@RequiredArgsConstructor
+@SuppressWarnings({
+ "JTCOP.RuleCorrectTestName",
+ "JTCOP.RuleAllTestsHaveProductionClass"
+})
+public final class KeycloakToken implements Scalar {
+
+ /**
+ * Auth server url.
+ */
+ private final String url;
+
+ @Override
+ @SneakyThrows
+ public String value() {
+ return new Jocument(
+ new JsonOf(
+ new JdkRequest(
+ "%s/realms/test/protocol/openid-connect/token".formatted(this.url)
+ ).method(Request.POST)
+ .body()
+ .formParam("grant_type", "password")
+ .formParam("client_id", "api")
+ .formParam("client_secret", "secret")
+ .formParam("username", "user")
+ .formParam("password", "test")
+ .back()
+ .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED)
+ .fetch()
+ .as(RestResponse.class)
+ .assertStatus(HttpURLConnection.HTTP_OK)
+ .body()
+ )
+ ).leaf("access_token");
+ }
+
+}
diff --git a/src/test/java/it/web/RetrieveProjectByIdITCase.java b/src/test/java/it/web/RetrieveProjectByIdITCase.java
new file mode 100644
index 0000000..936e626
--- /dev/null
+++ b/src/test/java/it/web/RetrieveProjectByIdITCase.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package it.web;
+
+import com.jcabi.http.Request;
+import com.jcabi.http.Response;
+import com.jcabi.http.request.JdkRequest;
+import git.tracehub.pmo.PmoApplication;
+import it.KeycloakIntegration;
+import it.PostgresIntegration;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * Integration Test Case for Retrieving Project by Id
+ * from {@link git.tracehub.pmo.controller.ProjectController}.
+ *
+ * @since 0.0.0
+ */
+@ActiveProfiles("pgit")
+@SpringBootTest(
+ classes = PmoApplication.class,
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
+)
+@SuppressWarnings("JTCOP.RuleAllTestsHaveProductionClass")
+final class RetrieveProjectByIdITCase
+ implements KeycloakIntegration, PostgresIntegration {
+
+ /**
+ * Raw Endpoint.
+ */
+ private static final String RAW = "http://localhost:%s/74bb5ec8-0e6b-4618-bfa4-a0b76b7b312d";
+
+ /**
+ * Application Port.
+ */
+ @LocalServerPort
+ private int port;
+
+ @Test
+ @Sql("classpath:pre/sql/projects.sql")
+ void retrievesProjectByIdSuccessfully() throws Exception {
+ final Response response = new JdkRequest(
+ RetrieveProjectByIdITCase.RAW.formatted(this.port)
+ ).method(Request.GET)
+ .header(
+ HttpHeaders.AUTHORIZATION,
+ "Bearer %s".formatted(
+ new KeycloakToken(
+ KeycloakIntegration.KEYCLOAK.getAuthServerUrl()
+ ).value()
+ )
+ ).fetch();
+ MatcherAssert.assertThat(
+ "Response Status %s does not match to expected one".formatted(
+ response.status()
+ ),
+ response.status(),
+ new IsEqual<>(200)
+ );
+ }
+
+}
diff --git a/src/test/java/it/web/RetrieveProjectsByUserITCase.java b/src/test/java/it/web/RetrieveProjectsByUserITCase.java
new file mode 100644
index 0000000..f08dcbf
--- /dev/null
+++ b/src/test/java/it/web/RetrieveProjectsByUserITCase.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package it.web;
+
+import com.jcabi.http.Request;
+import com.jcabi.http.Response;
+import com.jcabi.http.request.JdkRequest;
+import git.tracehub.pmo.PmoApplication;
+import it.KeycloakIntegration;
+import it.PostgresIntegration;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.core.IsEqual;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * Integration Test Case for Retrieving Project by User
+ * from {@link git.tracehub.pmo.controller.ProjectController}.
+ *
+ * @since 0.0.0
+ */
+@ActiveProfiles("pgit")
+@SpringBootTest(
+ classes = PmoApplication.class,
+ webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
+)
+@SuppressWarnings("JTCOP.RuleAllTestsHaveProductionClass")
+final class RetrieveProjectsByUserITCase
+ implements KeycloakIntegration, PostgresIntegration {
+
+ /**
+ * Raw Endpoint.
+ */
+ private static final String RAW = "http://localhost:%s";
+
+ /**
+ * Application Port.
+ */
+ @LocalServerPort
+ private int port;
+
+ @Test
+ @Sql("classpath:pre/sql/projects.sql")
+ void retrievesProjectByIdSuccessfully() throws Exception {
+ final Response response = new JdkRequest(
+ RetrieveProjectsByUserITCase.RAW.formatted(this.port)
+ ).method(Request.GET)
+ .header(
+ HttpHeaders.AUTHORIZATION,
+ "Bearer %s".formatted(
+ new KeycloakToken(
+ KeycloakIntegration.KEYCLOAK.getAuthServerUrl()
+ ).value()
+ )
+ ).fetch();
+ MatcherAssert.assertThat(
+ "Response Status %s does not match to expected one".formatted(
+ response.status()
+ ),
+ response.status(),
+ new IsEqual<>(200)
+ );
+ }
+
+}
diff --git a/src/test/java/it/web/package-info.java b/src/test/java/it/web/package-info.java
new file mode 100644
index 0000000..15c2aa6
--- /dev/null
+++ b/src/test/java/it/web/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2023-2024 Tracehub.git
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to read
+ * the Software only. Permissions is hereby NOT GRANTED to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * Web integrations.
+ *
+ * @since 0.0.0
+ */
+package it.web;
diff --git a/src/test/resources/analyze.sql b/src/test/resources/analyze.sql
new file mode 100644
index 0000000..64773a8
--- /dev/null
+++ b/src/test/resources/analyze.sql
@@ -0,0 +1 @@
+ANALYZE my_table
\ No newline at end of file
diff --git a/src/test/resources/application-pgit.yaml b/src/test/resources/application-pgit.yaml
new file mode 100644
index 0000000..f567314
--- /dev/null
+++ b/src/test/resources/application-pgit.yaml
@@ -0,0 +1,7 @@
+spring:
+ datasource:
+ username: test
+ password: test
+ liquibase:
+ user: test
+ password: test
diff --git a/src/test/resources/data/realm.json b/src/test/resources/data/realm.json
new file mode 100644
index 0000000..550b39f
--- /dev/null
+++ b/src/test/resources/data/realm.json
@@ -0,0 +1,1803 @@
+{
+ "id": "test",
+ "realm": "test",
+ "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,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "defaultRole": {
+ "id": "ae0de638-8b16-4d7f-9c1d-e1a55129f895",
+ "name": "default-roles-baeldung",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "baeldung"
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpSupportedApplications": [
+ "FreeOTP",
+ "Google Authenticator"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "f4acf69c-1490-444e-9b1a-dfa4016b54a1",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/baeldung/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/baeldung/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "cbfe941f-fe9a-4b79-aeb6-043b7993ffcc",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/baeldung/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/baeldung/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "bfff2bda-a626-489f-8b07-9989582dd452",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "6c504e4d-4ad5-48ee-a47b-6c31d8d9f67e",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "c683c33f-a584-46b1-af9f-d401ba93af85",
+ "clientId": "api",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "backchannel.logout.session.required": "true",
+ "backchannel.logout.revoke.offline.tokens": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "d71dc523-1f66-4cce-a23f-83a13ba3744b",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "36b87703-dfab-4c8e-b2a6-2bd96c89c141",
+ "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": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "440114dd-9b2c-4c4b-b192-8dec233715db",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/baeldung/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/baeldung/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "20f83504-601b-43d8-8725-38af0d7471d8",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "b7ffedbd-ba94-4fd4-ba1e-0145252e10ef",
+ "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": "7f93ba40-1f5e-48ff-b332-3cfe659072cb",
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "014cf82a-ac04-4fff-880b-019a225d3bbd",
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "0c36e47f-4e3d-4cad-aa18-5521d3bbc7f2",
+ "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": "abf78f87-6282-4a04-9ea6-4270978c7507",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "id": "171f5552-b4dc-45c1-b166-5738b7bbb08d",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "id": "92c00b3e-ec76-4497-aa56-3df225e82e24",
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "b1856e10-88e1-4f14-b9f0-6673523f6e39",
+ "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": "d94d0d20-6329-47ce-a4d4-b0f80d702ab7",
+ "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": "9dc9f261-26ed-4dab-bdbb-735f7a50f90e",
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "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": "b9e049bb-7cbf-483a-a55a-c777b7403229",
+ "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": "35bdeedc-92e7-484b-9282-70574247d263",
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "ab2aac65-a55e-406e-b42e-40d42e6c4388",
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "c8df543e-eb97-42e6-8aa2-63a8252afc9e",
+ "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": "b8714689-b19f-40da-b184-cc882028e8a4",
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "e00ad84f-2f3c-4f13-be8a-3931103042c6",
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "54a0ac8d-4b46-413a-8d63-1822b424e2f1",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "d52bd17c-98c1-4dcc-9e16-5736ddf6347e",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "18f13b5b-b7b3-4daa-afc5-ec25d6395075",
+ "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": "9371941e-b465-4413-a742-1036fafb2485",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "id": "09b1bbe9-5be6-477f-bcf5-bb661d7fe2a2",
+ "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": "58083ae1-5f33-4830-b975-e9f8da33920f",
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "6a87985d-12e6-4ea6-9e40-14fe46ad4668",
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d2393d95-5f3e-4f08-a876-d4971845e5ad",
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "7ce8e5e6-2f50-4b32-bd0a-7db014146baf",
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "e935814c-cf70-447d-85da-854bee14f403",
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "b2a73b0c-fd84-4894-85cf-05b9a8b9a329",
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "1f4d67e2-7083-4bb8-a666-361a6762161d",
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "5425cfb0-f259-43ba-bd31-8ab2b935e76c",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "2fb74508-49c3-46dd-b620-61a4a461c7bc",
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "457d4a3f-f47e-4ed4-a7da-5950c822e434",
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d2748ba4-aac9-486f-bbfc-ec398e273eec",
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "id": "6e78fd09-6cac-4c66-84e4-299b58a1eefc",
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "e44bd071-8a85-49a3-8649-09985f7e8a61",
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "da7000a8-8a8b-4df7-a4b9-7f78f3d13406",
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "profile",
+ "roles",
+ "web-origins",
+ "role_list",
+ "email"
+ ],
+ "defaultOptionalClientScopes": [
+ "offline_access",
+ "phone",
+ "microprofile-jwt",
+ "address"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "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": "80d5424d-79ab-44f4-818e-bc3b593641de",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "b578a18a-e340-49a1-ace1-8092ed29f28c",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-attribute-mapper",
+ "oidc-full-name-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "saml-user-property-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-role-list-mapper",
+ "oidc-address-mapper",
+ "saml-user-attribute-mapper"
+ ]
+ }
+ },
+ {
+ "id": "9311fd52-8330-4065-b987-7a0ce8589d6a",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "dcd5d2fe-da81-492e-ae6b-c026792e3154",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "1ed1d774-d6d9-45ee-8a22-61125a01a479",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "717825ce-f267-4468-99cb-b09126b00570",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "6fb4db12-299d-4b7e-a100-8c318248d5e3",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "bf2d5422-07a0-4317-9e01-0d2a1be9b582",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-property-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-user-attribute-mapper",
+ "oidc-address-mapper",
+ "oidc-full-name-mapper",
+ "saml-user-property-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "be18f5ef-4151-43e2-a607-1852f53acc76",
+ "name": "rsa-enc-generated",
+ "providerId": "rsa-enc-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "RSA-OAEP"
+ ]
+ }
+ },
+ {
+ "id": "52fb51ee-b5aa-41c3-88d2-a0b11b6d4072",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "9e839a58-a36e-420f-bebc-7466c4b578c9",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ },
+ {
+ "id": "93d3b307-cf5a-484c-a612-43cb4923501a",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "e285f1b9-bc15-4457-851d-abc9cb54953d",
+ "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": "65944e23-1f0d-47cf-b5d8-358463b0d382",
+ "alias": "Authentication Options",
+ "description": "Authentication options.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "basic-auth",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 30,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "id": "235ca7f6-27ad-4817-a686-cec020f7a091",
+ "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": "3d8dc16b-07e8-4535-ad1a-4ce79df1dd07",
+ "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": "3fa38b47-45a2-4eea-85a6-620b877db701",
+ "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": "126eee75-5f90-4a46-92c5-6fbb209d764a",
+ "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": "d9d8e610-41f8-45e3-a097-9961aa5e6d5f",
+ "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": "b01b7a9e-597b-4a12-8ccb-bfb707686dbf",
+ "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": "33a40ef5-e611-4182-b1a5-55aa930a839b",
+ "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": "5e148805-8a3f-4cbd-9a75-862da8e3fcdf",
+ "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": "38fd27d7-9914-47b3-aa11-1079cc059236",
+ "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": "58a3dcd5-56d1-4003-a1a1-dfafaf71ffa1",
+ "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": "4ed42c08-7dc6-47bf-8ac6-27e262973fdb",
+ "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": "b4ff61a6-56c7-4160-9047-b9abab13cfd9",
+ "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": "68b55fff-3f1e-4cce-a181-0b4fdc8ab1e5",
+ "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": "d3456a10-f6a1-4767-9385-baed652e82c4",
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Authentication Options",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "id": "7b50c4ff-25c2-4ff9-8f4b-95c1144bb30a",
+ "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": "edc22a19-8b8d-4bca-bdfb-446df319e4b5",
+ "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-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "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
+ }
+ ]
+ },
+ {
+ "id": "b5f2931c-badf-4c09-96fb-7a0af5860b9f",
+ "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": "cc118d9d-31b8-4fa9-ba7c-2fc65936612a",
+ "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": "5219b9c4-0664-4b61-8fd4-c9ecc9954c4b",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "f48e0b44-86c9-4db0-87da-97433d873b80",
+ "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": "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",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "oauth2DevicePollingInterval": "5",
+ "parRequestUriLifespan": "60",
+ "cibaInterval": "5"
+ },
+ "keycloakVersion": "17.0.1",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ },
+ "users": [
+ {
+ "username": "user",
+ "email": "user",
+ "firstName": "User",
+ "lastName": "User",
+ "enabled": true,
+ "credentials": [
+ {
+ "type": "password",
+ "value": "test"
+ }
+ ],
+ "clientRoles": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/pre/sql/projects.sql b/src/test/resources/pre/sql/projects.sql
new file mode 100644
index 0000000..de1d8fb
--- /dev/null
+++ b/src/test/resources/pre/sql/projects.sql
@@ -0,0 +1,16 @@
+INSERT INTO projects.projects (id,
+ name,
+ location,
+ description,
+ active)
+VALUES ('74bb5ec8-0e6b-4618-bfa4-a0b76b7b312d',
+ 'Test',
+ 'github@user/test:master',
+ 'Description',
+ true);
+INSERT INTO projects.performers (email,
+ project,
+ permission)
+VALUES ('user',
+ '74bb5ec8-0e6b-4618-bfa4-a0b76b7b312d',
+ 'READ');
\ No newline at end of file