diff --git a/software/plugins/image-search/README.md b/software/plugins/image-search/README.md index eeb4553710..406972ef11 100644 --- a/software/plugins/image-search/README.md +++ b/software/plugins/image-search/README.md @@ -1,5 +1,47 @@ # Image Search Plugin + + - store image vectors in mongodb + - pull image data from OQM + - present an endpoint to upload an image, search for matches + + + + + +## TODOS + + - [ ] Return actual search results, not image IDs Colten + - [ ] Scope each search to specific database Greg + - [ ] Process image changes as they happen, not just when DB is initialized Colten/Greg + - [ ] Redesign TreeMap (possibly different structure) to not store extra objects Colten + - [ ] Testing... + - [ ] Integration into deployment methodology Greg + - [ ] Memory leaks (Mat objects) + - [ ] OpenCV library optimization (System.loadLibrary()) + - [ ] Verify and ensure concurrent calls work in ImageSearchService + - [ ] Logging in ImageSearchService + - [ ] Check if new Java TF + +## LATER TODO + +- [ ] UI? +- [ ] potential batch-processing / multi-threading (optional) +- + + + + + + + + + + + + + + This plugin is intended to provide image-based searches of items. Use case is to take a picure of something, and then identify which items in the database it could be based on that image. diff --git a/software/plugins/image-search/build.gradle b/software/plugins/image-search/build.gradle index 4eccb2d683..533daf9ca8 100644 --- a/software/plugins/image-search/build.gradle +++ b/software/plugins/image-search/build.gradle @@ -3,6 +3,7 @@ import java.nio.file.Files plugins { id 'java' id 'io.quarkus' + id "io.freefair.lombok" version "8.14" } repositories { @@ -21,8 +22,20 @@ dependencies { implementation 'io.quarkus:quarkus-messaging-kafka' implementation 'io.quarkus:quarkus-config-yaml' implementation 'io.quarkus:quarkus-arc' + implementation 'io.quarkus:quarkus-oidc' + implementation 'io.quarkus:quarkus-smallrye-openapi' + implementation 'io.quarkus:quarkus-micrometer-opentelemetry' + + implementation 'tech.epic-breakfast-productions.openquartermaster.lib.core:core-api-lib-quarkus:4.4.4' + + implementation group: 'org.tensorflow', name: 'tensorflow-core-platform', version: '1.1.0' + // TODO:: apparently opencv-platform is more user friendly (dont have to call `System.loadLibrary()` for instance. Try it out and see if still works with tensorflow + implementation group: 'org.openpnp', name: 'opencv', version: '4.9.0-0' + + testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' + testImplementation 'net.datafaker:datafaker:2.4.4' } group 'tech.ebp.oqm.plugin' @@ -108,11 +121,11 @@ tasks.register("downloadModels", DownloadModelsTask) { group = "build" description = "Downloads models before build to ensure presence." } -build { +processResources { mustRunAfter("downloadModels") } -tasks.build.configure { dependsOn(tasks.downloadModels) } +tasks.processResources.configure { dependsOn(tasks.downloadModels) } /** * Used to print the current version of this project. diff --git a/software/plugins/image-search/dev/developers.jpeg b/software/plugins/image-search/dev/developers.jpeg new file mode 100644 index 0000000000..24daf42e56 Binary files /dev/null and b/software/plugins/image-search/dev/developers.jpeg differ diff --git a/software/plugins/image-search/dev/oqm-realm.json b/software/plugins/image-search/dev/oqm-realm.json new file mode 100644 index 0000000000..a4da1e68fc --- /dev/null +++ b/software/plugins/image-search/dev/oqm-realm.json @@ -0,0 +1,2390 @@ +{ + "id": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "realm": "oqm", + "displayName": "Open QuarterMaster", + "displayNameHtml": "Open QuarterMaster", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 1500, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 3600, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 691200, + "ssoSessionMaxLifespanRememberMe": 2592000, + "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": true, + "registrationEmailAsUsername": false, + "rememberMe": true, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": true, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "e6a994ae-dbaf-4e48-bb56-ba840451ab37", + "name": "default-roles-oqm", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "abc50c50-5c5e-40c4-8a02-36b393e37f34", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "f02b2aab-a653-4587-9ac9-4bdfb0ce0d42", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "e85b5fbd-6309-4976-a223-debe0fe849fd", + "name": "user", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "55a4bac9-606a-41d2-8614-961c48688a8a", + "name": "inventoryEdit", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "85f863da-af3a-487b-b23f-322e0872a3e8", + "name": "itemCheckout", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "cba5cb2c-60a6-434f-aa9c-5ec8b053c4d1", + "name": "inventoryAdmin", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + }, + { + "id": "338936d4-10d1-437d-aa42-33aa16d1ccc2", + "name": "inventoryView", + "description": "", + "composite": false, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56", + "attributes": {} + } + ], + "client": { + "oqm-app": [], + "realm-management": [ + { + "id": "494f1f69-cc42-4327-8861-19adc3dc439e", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "db013aff-77c2-495b-86c6-032f89645588", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "1e27d7c6-cf36-4a10-bcdf-b9e24b8422b8", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "ec65b046-8db5-49d4-814c-4906b430adbb", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "878821f8-e054-4023-a376-06432d69be05", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "421f3fde-25d8-463e-b5fe-92f5d335ecd6", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "b27e0401-c019-48d9-a10a-92a0d2b717ab", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "11029fb6-9771-48ec-9f91-ce2fee572a6f", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-events", + "query-users", + "manage-events", + "create-client", + "view-realm", + "view-authorization", + "manage-realm", + "view-identity-providers", + "query-realms", + "impersonation", + "manage-authorization", + "view-clients", + "query-groups", + "manage-clients", + "view-users", + "query-clients", + "manage-identity-providers", + "manage-users" + ] + } + }, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "4108092e-3d47-4484-a927-387280ce5ba6", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "e491ffa9-cf6f-4033-b27d-a1a6236c1d96", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "1da68065-fd4d-4585-b0c5-910581c652a6", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "d22fc947-c2f4-40a5-8d8b-f56defd8c914", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "e4c561ab-ad46-4e70-bffa-f89fa2981308", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "b7bcdb59-d901-47e9-ac3e-73e9f5604e4a", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "2f64bd46-a5ca-4f8d-ba54-43ab0fdd3130", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "907c4477-eef2-4990-96fd-ffcfd4223a14", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "8fee0a0f-6d30-4ac5-adf6-c19ae8504565", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "6cd044a8-6be7-4ce3-9d2a-d4d1835428f0", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + }, + { + "id": "2a7d049b-66c5-4419-b55a-6f98e5fa5ab8", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "62772859-0242-4ec5-b09d-9910e9e211ef", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "0bc641d3-cc81-48de-b0a5-095251d54b88", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "3e4ca529-f17c-4959-9f34-4fefef4f30ba", + "attributes": {} + } + ], + "account": [ + { + "id": "eaf4c4c0-c39b-40c6-81d5-ee08fb6c8d5d", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "07ee4398-9f0b-43b7-83fa-a05837120c6d", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "1d7ad9a5-dd20-4dd5-a3c6-bc3e106fc28e", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "65fefd92-20a3-4fd6-b815-264f54196c76", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "2d9e69c3-3b95-4935-8ac5-fa9dfb1a8582", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "24fd848a-6550-44d2-bfd5-505d918bd00c", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "ce6d9cdd-4528-4a0f-ac48-b1d5e48ca734", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + }, + { + "id": "3c46638b-88bf-490a-8d52-5a55fb7b92ec", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "c060b435-d3a6-4408-9438-0774759f93be", + "attributes": {} + } + ] + } + }, + "groups": [ + { + "id": "f3d221e9-c54e-4182-b9a0-689c736d97d9", + "name": "users", + "path": "/users", + "attributes": {}, + "realmRoles": [ + "user", + "inventoryEdit", + "itemCheckout", + "inventoryView" + ], + "clientRoles": {}, + "subGroups": [ + { + "id": "1ceab6a9-cdf7-4271-a812-0e9f0dc74dca", + "name": "admins", + "path": "/users/admins", + "attributes": {}, + "realmRoles": [ + "inventoryAdmin" + ], + "clientRoles": {}, + "subGroups": [] + } + ] + } + ], + "defaultRole": { + "id": "e6a994ae-dbaf-4e48-bb56-ba840451ab37", + "name": "default-roles-oqm", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "72bd1a2f-d711-48ce-b65b-0ad7107e8d56" + }, + "defaultGroups": [ + "/users/admins" + ], + "requiredCredentials": [ + "password" + ], + "passwordPolicy": "length(8) and notUsername(undefined) and notEmail(undefined) and specialChars(1) and upperCase(1) and lowerCase(1) and digits(1)", + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppMicrosoftAuthenticatorName", + "totpAppGoogleName", + "totpAppFreeOTPName" + ], + "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": [], + "users": [ + { + "id": "af46bb6b-dbb2-43d8-8a6e-a3e4d9185df3", + "createdTimestamp": 1691685755844, + "username": "service-account-oqm-app", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "oqm-app", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-oqm", + "inventoryEdit", + "itemCheckout", + "inventoryAdmin", + "inventoryView" + ], + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "c060b435-d3a6-4408-9438-0774759f93be", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/oqm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/oqm/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", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "68e976a2-9891-4bec-a11a-35da93e23523", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/oqm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/oqm/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": "df627fc5-cae4-4e8b-a0cd-fc21c196717d", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c47ce71b-a9d7-4c04-9c72-6baf4cafbed6", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "3e4ca529-f17c-4959-9f34-4fefef4f30ba", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ea4470e2-e385-4d3f-833a-266814280437", + "clientId": "oqm-app", + "name": "Open QuarterMaster App", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": true, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1691685755", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "95320d8d-c6c5-4c8a-82a7-46560714f524", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "14950178-47b3-45f0-8b02-86f2d5115dbe", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "4cafaebb-a867-45e9-86ad-3ec7f5fafbb1", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "microprofile-jwt", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access" + ] + }, + { + "id": "62772859-0242-4ec5-b09d-9910e9e211ef", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "140ac0aa-f51f-4330-9a37-1095e43656e8", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/oqm/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/oqm/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": "de8193dd-50af-4908-acc5-0312328305c1", + "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", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "655d834d-fb4f-4626-97bd-95b6d8defcf6", + "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": "611f32d4-4fd3-45d7-bae8-6dce0cfcaefe", + "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": "9e2cbb13-ef9e-43b3-90c9-c77f8599fb22", + "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": "ac83bab5-9cd8-4e32-bee7-61426fefa5c5", + "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": "2e55d475-3581-4773-b447-3a3809e5eafa", + "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": "11eae81d-4e36-41dd-b944-e44db7ce0901", + "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": "beac488b-bc94-4ac9-9000-46dd2dc79ecd", + "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": "cd873466-8383-4dcf-b3e4-77c0f6d2d3ca", + "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": "e0558f02-9535-43d4-bf17-95cfb814d826", + "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": "59159538-9543-40c5-a62b-b847273846b5", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "9860102f-4701-4c3f-89c8-e70ab0aa54b3", + "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": "0ebd037a-ff0b-44fe-9d7b-8fefcb9c3bf8", + "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": "6b17545b-a9cc-4f64-be3e-aaa72aad13e9", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "c959b970-c65d-4a73-8452-cf77963b8a9d", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "ea04d515-fe65-4bbe-a6c6-c3e1ffb7807f", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "b22fd8e1-8674-4ab6-87a7-ab479e59f373", + "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": "295d6396-f43b-40cf-a518-d214a7e6cd76", + "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": "5e9bdb1f-0900-4b18-93f4-477d8b53e749", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "06ec2178-5d2a-4f06-ac35-0e66af862578", + "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": "5d28913f-0785-42f0-a861-dd1fdf88dbb5", + "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": "f42e0caa-c451-4778-a9ab-5879077292f6", + "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": "235480eb-c061-484b-b0d1-ae942496fc82", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0eefc9fe-91bb-47eb-b3a2-e85c3eed9789", + "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": "924b9161-4ee2-4953-ac7f-d675a5563a4c", + "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": "869bd4e4-7a38-4ab0-a0e4-20c3cf48b7e8", + "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": "long" + } + }, + { + "id": "019b42ff-bc0b-45c9-8038-3f9616559cfd", + "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": "385ed7ba-0053-4a9b-8004-4690447f6e56", + "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": "43335897-41f7-4ba3-8216-199ac09a2c7f", + "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": "cc772c28-4edc-47b1-8994-43de0f5155a6", + "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" + } + }, + { + "id": "4f2d1b94-e883-4a5a-85e9-08ccc1ac943b", + "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": "5520932e-97e2-40e8-b2be-42354e824469", + "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": "906963b9-3c74-44e9-b0eb-8e1edd5aca9a", + "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": "2cc856eb-d838-4426-9b7a-706548310d33", + "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": "878e5766-f863-4889-bbb5-98a63c696ddd", + "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": "64ac959c-f005-41d6-b5e2-24af616f9954", + "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": "b48e1f36-735c-4909-897e-154c1f18c226", + "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": "4a8d8f88-928e-4795-a874-1857d88a4cbb", + "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" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "microprofile-jwt" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone" + ], + "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": "8497b35a-9ff3-4a86-9caa-de9f79b84ed9", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "a74c491f-76da-41f0-bd6e-7ed497b55258", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "2026f90a-e6b4-4592-8653-bf9905106a1a", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "7a5d3644-3d07-47bd-826e-2fb948632d05", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "3581cd3d-81ae-45ce-a020-a193fe6c9e31", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "75485a58-8c10-4710-a087-2c4b2522a68b", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "0d599030-dbe7-4457-8273-a7cd7e66cc74", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "7d1ddc52-dc09-45ef-be63-bb8c56d5aba6", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "cb730612-ca1a-4483-b312-19e828e47868", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "d5002cc5-51a9-42ce-af8c-f26a75195f38", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": ["MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDEbL+Yf3DHbqM8\nkqd60sSZIZwaQ6Z/OVYREDDyb7t6slcXUGATyG1OuuHpEkpRJ52EKljeOuQI1tL+\na874AnLFQ58sQG/coABe5li+0U2j0KhjW+1pV6YWY42N7iM4ihRFxy4T5EUiGIfQ\nS8sqxAX5r3grYNy45sNkcpxTaBUn4nX+FwpIrlbeKUk2olFnYYvjzYiaUHEyW5bC\nclDFfd0D0L/ud3av7WzuPlttK28jscyfjVqP9qmz/aX3YBaoSur2VmiNv0Jk+KAw\nfkoB3WdevSon2ys/ArcyKUIEr+2rGIqFzbs1Jr1qbGbNCtydM9KxDT/Uovhzjn+X\nq/WPAAWtAgMBAAECggEABPVGitBR77nee4CrZ/ezKqNxbqLnDuI3MvwQODtTPjPQ\nTpVPJbHrp4Ai+mLvTiWngBlIKdQxQtNlXajHEycFl3hvTvI4+7RFJYKOcv3/I4Tu\ntaa7f3TglS+OM4OG+4fzgSB6Pcau4SG3dhBGynitE1IyXji2M3B553R1AQa80uTT\nup17Aa9szQlF6YV8ZHJz02ydFJu+r+f305EYu1S0Z/U1R3mIjCkNXQCkw7RZ0qxO\nr9aLYXhk8rWEGsOwgRtwnnwjepXeXWI1Ca41WCc1/LLodTmXawCsyeYY2VoYEneR\nAo0V0KlgQx7B/EsUiqk8/Gvcp8ZPb4x12FRP8B4DcwKBgQD/yz2YS+Ss0mlX3B5N\nBFlRC6RvawVCFjx+m+7XboHnuf+wikBGp8RT+OuXuzFj+Wc8hoNeHdohDzJENs7q\nNZrFYnh1+WDv/QGf9KdmrntQspCiLHTINR0B/LYFec9k907eVOmUKerho3f/M0Ab\nhvpYaQBK+WaF253aop53r6ag8wKBgQDElUMy5Q/YlBL09mbLXxYRToxuFDiuZibc\ngyZkgdkjYQeMpWaXcnJ8zlMv95XBM85fe8OhOUBdhgpOG+bxB/7tffEVMn5+eJz+\n274Wl/QCUaMQao0crwtEbOwbOzT5cODONkrzpvGBpasQ6hRt7iX2U1LHbX4+aqZV\nKXoiGqNm3wKBgGky5AUjiUuOSabJr2iLVlRfjmQIRqRUUtLbPJI7L4/mzgKECUVF\nsBe88t93LCvqoYuh4pstec1I26p0RArMuvdctSAHzNdGXYm0a7huH+cjWRppYCoK\ntgBgN1fvLd1fXH9RurKlvqTHvw0kvcPUclcz79vl0EVS+gu1/6hHhCcDAoGBAJj0\nSkTfVWT+UYGn9nNmmJT+uOUtdqy7bqFEqiqpnXmZlXYpe5l6wvm4z6ES0sJwvLIu\nahiXoy0hjgMYUqhXwFKpG1uS3jkpP35NG6oYsRLc0jODtCgNSocC+PJ3LtCms0O/\nmrHZwy9M571RZHPkSEVQr6fb2c5WzPSWQSEn+NuhAoGACgLLLWNhx6etLEVVQBdW\nHPe4yFlOdEkPNmQdU0JlRl5DdFQWxsWRUJWopS/0K/bmUDNZ5+95leVJ/kpCrRhL\nMuohkBpw9jCMXcdw1jHWVQGAXhCnmDES2Obv2Rvgmy9XUno/xOtabjw8n0lvt0Le\nsncB7P++ck6iDWRd/v9h4LA="], + "keyUse": ["SIG"], + "certificate": ["MIIDjTCCAnWgAwIBAgIUVGR2mw8+0KhIDl05mmSm1gZPFfQwDQYJKoZIhvcNAQEL\nBQAwVjELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCVRlc3RTdGF0ZTERMA8GA1UEBwwI\nVGVzdFRvd24xDDAKBgNVBAoMA09RTTESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0\nMDIwNzA4MDUzOFoXDTM0MDIwNDA4MDUzOFowVjELMAkGA1UEBhMCVVMxEjAQBgNV\nBAgMCVRlc3RTdGF0ZTERMA8GA1UEBwwIVGVzdFRvd24xDDAKBgNVBAoMA09RTTES\nMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAxGy/mH9wx26jPJKnetLEmSGcGkOmfzlWERAw8m+7erJXF1BgE8htTrrh6RJK\nUSedhCpY3jrkCNbS/mvO+AJyxUOfLEBv3KAAXuZYvtFNo9CoY1vtaVemFmONje4j\nOIoURccuE+RFIhiH0EvLKsQF+a94K2DcuObDZHKcU2gVJ+J1/hcKSK5W3ilJNqJR\nZ2GL482ImlBxMluWwnJQxX3dA9C/7nd2r+1s7j5bbStvI7HMn41aj/aps/2l92AW\nqErq9lZojb9CZPigMH5KAd1nXr0qJ9srPwK3MilCBK/tqxiKhc27NSa9amxmzQrc\nnTPSsQ0/1KL4c45/l6v1jwAFrQIDAQABo1MwUTAdBgNVHQ4EFgQUZQgkSnGsP6Mc\nkynYGaOWJYkwQbswHwYDVR0jBBgwFoAUZQgkSnGsP6MckynYGaOWJYkwQbswDwYD\nVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArPlxP5eHtpTSCk/ANJiu\n5DuuHGp4w8Wr/Vcbd2N5owAx5R7sD0JeMYToNOA4ecLZCdc9/pnrCNjBN5QAWOWm\nOC1cUMusODJZTwLarH3aDiOMrLnc3uzNoMYsV51zEfdrTk4o0qc71mkUXzMt8TY1\n0m2mVgWZFRlsykhMpjmTN4Ph/Gz2Y+MSSHbBQ4PRSkcZxZQYvroju1BwT1yW2eLX\n1OBX2WLMaYSm7KPNCad3zc9XRHJFuANWvQdmsoRc2trirVh8X2IcMnMHb/WARLRA\neYApCXlXPGh5nwbKe6l8UN02/+M766GZH/w6wLnjn6z/xDZVVGubkZgI6y2v516+\n9Q=="], + "priority": [ + "100" + ] + } + }, + { + "id": "958fd27f-3bf4-4690-9634-da3806c611cc", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "e841aa8f-ef4b-4892-adff-d18f96d8cdc8", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "a9a1e9c5-0c0f-4c4f-8928-22bc5c2396aa", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "915646bc-67cc-41c9-a973-3f709dceb8e5", + "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": "e1561238-53b2-40b7-8639-86a5893b7f00", + "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": "cd1fa446-559c-4980-bdd9-0417d0b61221", + "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": "4704ed9b-a832-427f-be2f-c789020db059", + "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": "d6ecaf6b-97c8-4e52-b86a-b335dfee8633", + "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": "31d64b4c-d6dd-41fc-a729-e6085bee9277", + "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": "7cce961e-1493-435d-9b4e-68fbe0b221b9", + "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": "75361f7d-3dbd-461d-8a0e-92b8a684a461", + "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": "0411b55a-b7b1-4747-832d-515994f3393c", + "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": "f876fc67-72db-49b4-8afb-d35daf474bc3", + "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": "ffe6ea54-ff6a-48b1-a31d-8b9a2a4583a3", + "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": "638d5e16-01ed-4055-88e9-580ce64eb6da", + "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": "0ea9d80d-f5ce-46cd-a59a-92aa2511ccf1", + "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": "1d148096-d236-4584-abcb-6e8e175217bc", + "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": "0945f168-0801-4b95-92ed-86214451f143", + "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": "03af4d48-9f86-4f1b-848b-dc68aa09485c", + "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": "18d42255-8784-4d28-a8a0-5c175db0d4f8", + "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": "388067b8-beaa-4ea4-b692-a73aa7b316df", + "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": "9489a78e-19fc-4809-8b59-918e63e2202b", + "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": "09d1a41d-9891-4dc0-911d-97856c6adc2e", + "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": "b614dc4c-7b83-421e-9c9e-863e84b8b931", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "f1125438-23fd-4d63-b1e5-0dbd9357205d", + "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", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "actionTokenGeneratedByUserLifespan-execute-actions": "", + "actionTokenGeneratedByUserLifespan-verify-email": "", + "clientOfflineSessionIdleTimeout": "0", + "actionTokenGeneratedByUserLifespan-reset-credentials": "", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "actionTokenGeneratedByUserLifespan-idp-verify-account-via-email": "", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "", + "acr.loa.map": "{}", + "shortVerificationUri": "" + }, + "keycloakVersion": "21.0.2", + "userManagedAccessAllowed": true, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/software/plugins/image-search/dev/testImages/Pliers.jpg b/software/plugins/image-search/dev/testImages/Pliers.jpg new file mode 100644 index 0000000000..c49d43fbe7 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/Pliers.jpg differ diff --git a/software/plugins/image-search/dev/testImages/Screw.jpg b/software/plugins/image-search/dev/testImages/Screw.jpg new file mode 100644 index 0000000000..596092fd92 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/Screw.jpg differ diff --git a/software/plugins/image-search/dev/testImages/Screw2.jpg b/software/plugins/image-search/dev/testImages/Screw2.jpg new file mode 100644 index 0000000000..a3ee2bd2b1 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/Screw2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/Shovel.jpg b/software/plugins/image-search/dev/testImages/Shovel.jpg new file mode 100644 index 0000000000..7a506ae5b8 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/Shovel.jpg differ diff --git a/software/plugins/image-search/dev/testImages/Wirestrippers.jpg b/software/plugins/image-search/dev/testImages/Wirestrippers.jpg new file mode 100644 index 0000000000..bddd8aacbe Binary files /dev/null and b/software/plugins/image-search/dev/testImages/Wirestrippers.jpg differ diff --git a/software/plugins/image-search/dev/testImages/adjustable_wrench.jpg b/software/plugins/image-search/dev/testImages/adjustable_wrench.jpg new file mode 100644 index 0000000000..aabf4b794d Binary files /dev/null and b/software/plugins/image-search/dev/testImages/adjustable_wrench.jpg differ diff --git a/software/plugins/image-search/dev/testImages/banana.jpg b/software/plugins/image-search/dev/testImages/banana.jpg new file mode 100644 index 0000000000..048e905b94 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/banana.jpg differ diff --git a/software/plugins/image-search/dev/testImages/bin.jpg b/software/plugins/image-search/dev/testImages/bin.jpg new file mode 100644 index 0000000000..6e99c94211 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/bin.jpg differ diff --git a/software/plugins/image-search/dev/testImages/bin2.jpg b/software/plugins/image-search/dev/testImages/bin2.jpg new file mode 100644 index 0000000000..84b5c894d8 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/bin2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/bolt.jpg b/software/plugins/image-search/dev/testImages/bolt.jpg new file mode 100644 index 0000000000..c104639faa Binary files /dev/null and b/software/plugins/image-search/dev/testImages/bolt.jpg differ diff --git a/software/plugins/image-search/dev/testImages/box.jpg b/software/plugins/image-search/dev/testImages/box.jpg new file mode 100644 index 0000000000..9a8a9a153c Binary files /dev/null and b/software/plugins/image-search/dev/testImages/box.jpg differ diff --git a/software/plugins/image-search/dev/testImages/box2.jpg b/software/plugins/image-search/dev/testImages/box2.jpg new file mode 100644 index 0000000000..9fb96cdbf4 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/box2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/box3.jpg b/software/plugins/image-search/dev/testImages/box3.jpg new file mode 100644 index 0000000000..7479d5f9e6 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/box3.jpg differ diff --git a/software/plugins/image-search/dev/testImages/cayenne.jpg b/software/plugins/image-search/dev/testImages/cayenne.jpg new file mode 100644 index 0000000000..ccb1347e6a Binary files /dev/null and b/software/plugins/image-search/dev/testImages/cayenne.jpg differ diff --git a/software/plugins/image-search/dev/testImages/cherry.jpg b/software/plugins/image-search/dev/testImages/cherry.jpg new file mode 100644 index 0000000000..fa5431130a Binary files /dev/null and b/software/plugins/image-search/dev/testImages/cherry.jpg differ diff --git a/software/plugins/image-search/dev/testImages/electric_jigsaw.jpg b/software/plugins/image-search/dev/testImages/electric_jigsaw.jpg new file mode 100644 index 0000000000..2e2fa5d247 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/electric_jigsaw.jpg differ diff --git a/software/plugins/image-search/dev/testImages/fork.jpg b/software/plugins/image-search/dev/testImages/fork.jpg new file mode 100644 index 0000000000..ca0b8e49ab Binary files /dev/null and b/software/plugins/image-search/dev/testImages/fork.jpg differ diff --git a/software/plugins/image-search/dev/testImages/gloves.jpg b/software/plugins/image-search/dev/testImages/gloves.jpg new file mode 100644 index 0000000000..1a2dd7e794 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/gloves.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hammer.jpg b/software/plugins/image-search/dev/testImages/hammer.jpg new file mode 100644 index 0000000000..8035d85b80 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hammer.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hammer_tool_1.jpg b/software/plugins/image-search/dev/testImages/hammer_tool_1.jpg new file mode 100644 index 0000000000..a5b8b8e9b0 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hammer_tool_1.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hammer_tool_5.jpg b/software/plugins/image-search/dev/testImages/hammer_tool_5.jpg new file mode 100644 index 0000000000..cf509d0429 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hammer_tool_5.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hand_held_rake.jpg b/software/plugins/image-search/dev/testImages/hand_held_rake.jpg new file mode 100644 index 0000000000..247a3d3a7d Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hand_held_rake.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hoe.jpg b/software/plugins/image-search/dev/testImages/hoe.jpg new file mode 100644 index 0000000000..b79c319be4 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hoe.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hose.jpg b/software/plugins/image-search/dev/testImages/hose.jpg new file mode 100644 index 0000000000..96348ec050 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hose.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hoses.jpg b/software/plugins/image-search/dev/testImages/hoses.jpg new file mode 100644 index 0000000000..6ba377d06c Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hoses.jpg differ diff --git a/software/plugins/image-search/dev/testImages/hoses2.jpg b/software/plugins/image-search/dev/testImages/hoses2.jpg new file mode 100644 index 0000000000..840a88c893 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/hoses2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/knife.jpg b/software/plugins/image-search/dev/testImages/knife.jpg new file mode 100644 index 0000000000..b7f8e67622 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/knife.jpg differ diff --git a/software/plugins/image-search/dev/testImages/knife2.jpg b/software/plugins/image-search/dev/testImages/knife2.jpg new file mode 100644 index 0000000000..835e7e1c5d Binary files /dev/null and b/software/plugins/image-search/dev/testImages/knife2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/lantern.jpg b/software/plugins/image-search/dev/testImages/lantern.jpg new file mode 100644 index 0000000000..e171c44a3a Binary files /dev/null and b/software/plugins/image-search/dev/testImages/lantern.jpg differ diff --git a/software/plugins/image-search/dev/testImages/lantern2.jpg b/software/plugins/image-search/dev/testImages/lantern2.jpg new file mode 100644 index 0000000000..ae8ab07901 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/lantern2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/lantern3.jpg b/software/plugins/image-search/dev/testImages/lantern3.jpg new file mode 100644 index 0000000000..4ccb1a0aba Binary files /dev/null and b/software/plugins/image-search/dev/testImages/lantern3.jpg differ diff --git a/software/plugins/image-search/dev/testImages/ledduce.jpg b/software/plugins/image-search/dev/testImages/ledduce.jpg new file mode 100644 index 0000000000..908d1c9571 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/ledduce.jpg differ diff --git a/software/plugins/image-search/dev/testImages/lightbulb.jpg b/software/plugins/image-search/dev/testImages/lightbulb.jpg new file mode 100644 index 0000000000..550d37ac70 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/lightbulb.jpg differ diff --git a/software/plugins/image-search/dev/testImages/lights.jpg b/software/plugins/image-search/dev/testImages/lights.jpg new file mode 100644 index 0000000000..cde5a7153c Binary files /dev/null and b/software/plugins/image-search/dev/testImages/lights.jpg differ diff --git a/software/plugins/image-search/dev/testImages/mango.jpg b/software/plugins/image-search/dev/testImages/mango.jpg new file mode 100644 index 0000000000..3482ecbcf4 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/mango.jpg differ diff --git a/software/plugins/image-search/dev/testImages/mustard.jpg b/software/plugins/image-search/dev/testImages/mustard.jpg new file mode 100644 index 0000000000..ca38994e3a Binary files /dev/null and b/software/plugins/image-search/dev/testImages/mustard.jpg differ diff --git a/software/plugins/image-search/dev/testImages/needle_nose_pliers.jpg b/software/plugins/image-search/dev/testImages/needle_nose_pliers.jpg new file mode 100644 index 0000000000..089c7cc351 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/needle_nose_pliers.jpg differ diff --git a/software/plugins/image-search/dev/testImages/nut.jpg b/software/plugins/image-search/dev/testImages/nut.jpg new file mode 100644 index 0000000000..b5bfc5ac65 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/nut.jpg differ diff --git a/software/plugins/image-search/dev/testImages/open_end_wrench.jpg b/software/plugins/image-search/dev/testImages/open_end_wrench.jpg new file mode 100644 index 0000000000..1da71b5099 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/open_end_wrench.jpg differ diff --git a/software/plugins/image-search/dev/testImages/oregano.jpg b/software/plugins/image-search/dev/testImages/oregano.jpg new file mode 100644 index 0000000000..a481c3309c Binary files /dev/null and b/software/plugins/image-search/dev/testImages/oregano.jpg differ diff --git a/software/plugins/image-search/dev/testImages/paintroller.jpg b/software/plugins/image-search/dev/testImages/paintroller.jpg new file mode 100644 index 0000000000..7a80cdab70 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/paintroller.jpg differ diff --git a/software/plugins/image-search/dev/testImages/paintroller2.jpg b/software/plugins/image-search/dev/testImages/paintroller2.jpg new file mode 100644 index 0000000000..221cc1f76e Binary files /dev/null and b/software/plugins/image-search/dev/testImages/paintroller2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/paprika.jpg b/software/plugins/image-search/dev/testImages/paprika.jpg new file mode 100644 index 0000000000..6f9b1ee4fb Binary files /dev/null and b/software/plugins/image-search/dev/testImages/paprika.jpg differ diff --git a/software/plugins/image-search/dev/testImages/pepper.jpg b/software/plugins/image-search/dev/testImages/pepper.jpg new file mode 100644 index 0000000000..769091d830 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/pepper.jpg differ diff --git a/software/plugins/image-search/dev/testImages/pineappple.jpg b/software/plugins/image-search/dev/testImages/pineappple.jpg new file mode 100644 index 0000000000..002d3476f2 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/pineappple.jpg differ diff --git a/software/plugins/image-search/dev/testImages/rake.jpg b/software/plugins/image-search/dev/testImages/rake.jpg new file mode 100644 index 0000000000..12aae1eedb Binary files /dev/null and b/software/plugins/image-search/dev/testImages/rake.jpg differ diff --git a/software/plugins/image-search/dev/testImages/rake2.jpg b/software/plugins/image-search/dev/testImages/rake2.jpg new file mode 100644 index 0000000000..7ad376284c Binary files /dev/null and b/software/plugins/image-search/dev/testImages/rake2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/screwdriver.jpg b/software/plugins/image-search/dev/testImages/screwdriver.jpg new file mode 100644 index 0000000000..b098bfa6b4 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/screwdriver.jpg differ diff --git a/software/plugins/image-search/dev/testImages/screwdriver_2.jpg b/software/plugins/image-search/dev/testImages/screwdriver_2.jpg new file mode 100644 index 0000000000..84896a2453 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/screwdriver_2.jpg differ diff --git a/software/plugins/image-search/dev/testImages/screwdriver_3.jpg b/software/plugins/image-search/dev/testImages/screwdriver_3.jpg new file mode 100644 index 0000000000..48b101b366 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/screwdriver_3.jpg differ diff --git a/software/plugins/image-search/dev/testImages/seasoning.jpg b/software/plugins/image-search/dev/testImages/seasoning.jpg new file mode 100644 index 0000000000..45ee76999d Binary files /dev/null and b/software/plugins/image-search/dev/testImages/seasoning.jpg differ diff --git a/software/plugins/image-search/dev/testImages/spoon.jpg b/software/plugins/image-search/dev/testImages/spoon.jpg new file mode 100644 index 0000000000..b87a0cc9da Binary files /dev/null and b/software/plugins/image-search/dev/testImages/spoon.jpg differ diff --git a/software/plugins/image-search/dev/testImages/trowel.jpg b/software/plugins/image-search/dev/testImages/trowel.jpg new file mode 100644 index 0000000000..c034f3c1b6 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/trowel.jpg differ diff --git a/software/plugins/image-search/dev/testImages/utility_knife.jpg b/software/plugins/image-search/dev/testImages/utility_knife.jpg new file mode 100644 index 0000000000..76eae23096 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/utility_knife.jpg differ diff --git a/software/plugins/image-search/dev/testImages/washer.jpg b/software/plugins/image-search/dev/testImages/washer.jpg new file mode 100644 index 0000000000..d8331958bb Binary files /dev/null and b/software/plugins/image-search/dev/testImages/washer.jpg differ diff --git a/software/plugins/image-search/dev/testImages/watch.jpg b/software/plugins/image-search/dev/testImages/watch.jpg new file mode 100644 index 0000000000..a13e69e979 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/watch.jpg differ diff --git a/software/plugins/image-search/dev/testImages/watermelon.jpg b/software/plugins/image-search/dev/testImages/watermelon.jpg new file mode 100644 index 0000000000..a06a8f7341 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/watermelon.jpg differ diff --git a/software/plugins/image-search/dev/testImages/wirecutters.jpg b/software/plugins/image-search/dev/testImages/wirecutters.jpg new file mode 100644 index 0000000000..9298f0acf1 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/wirecutters.jpg differ diff --git a/software/plugins/image-search/dev/testImages/woodscrew.jpg b/software/plugins/image-search/dev/testImages/woodscrew.jpg new file mode 100644 index 0000000000..c8c1a85115 Binary files /dev/null and b/software/plugins/image-search/dev/testImages/woodscrew.jpg differ diff --git a/software/plugins/image-search/dev/testImages/wrench.jpg b/software/plugins/image-search/dev/testImages/wrench.jpg new file mode 100644 index 0000000000..7ca9a2c34a Binary files /dev/null and b/software/plugins/image-search/dev/testImages/wrench.jpg differ diff --git a/software/plugins/image-search/dev/testImages/zuchinni.jpg b/software/plugins/image-search/dev/testImages/zuchinni.jpg new file mode 100644 index 0000000000..79464193cd Binary files /dev/null and b/software/plugins/image-search/dev/testImages/zuchinni.jpg differ diff --git a/software/plugins/image-search/gradle.properties b/software/plugins/image-search/gradle.properties index 2d667a17be..968a9872de 100644 --- a/software/plugins/image-search/gradle.properties +++ b/software/plugins/image-search/gradle.properties @@ -1,7 +1,7 @@ # Gradle properties quarkusPluginId=io.quarkus -quarkusPluginVersion=3.22.3 +quarkusPluginVersion=3.34.3 +quarkusPlatformVersion=3.34.3 quarkusPlatformGroupId=io.quarkus.platform quarkusPlatformArtifactId=quarkus-bom -quarkusPlatformVersion=3.22.3 diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingResource.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingResource.java deleted file mode 100644 index 429871e317..0000000000 --- a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingResource.java +++ /dev/null @@ -1,16 +0,0 @@ -package tech.ebp.oqm.plugin.imageSearch; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -@Path("/hello") -public class GreetingResource { - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String hello() { - return "Hello from Quarkus REST"; - } -} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplication.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplication.java deleted file mode 100644 index 9de4374803..0000000000 --- a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplication.java +++ /dev/null @@ -1,43 +0,0 @@ -package tech.ebp.oqm.plugin.imageSearch; - -import io.quarkus.runtime.StartupEvent; -import org.eclipse.microprofile.reactive.messaging.*; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.inject.Inject; -import java.util.stream.Stream; - -@ApplicationScoped -public class MyMessagingApplication { - - @Inject - @Channel("words-out") - Emitter emitter; - - /** - * Sends message to the "words-out" channel, can be used from a JAX-RS resource or any bean of your application. - * Messages are sent to the broker. - **/ - void onStart(@Observes StartupEvent ev) { - Stream.of("Hello", "with", "Quarkus", "Messaging", "message").forEach(string -> emitter.send(string)); - } - - /** - * Consume the message from the "words-in" channel, uppercase it and send it to the uppercase channel. - * Messages come from the broker. - **/ - @Incoming("words-in") - @Outgoing("uppercase") - public Message toUpperCase(Message message) { - return message.withPayload(message.getPayload().toUpperCase()); - } - - /** - * Consume the uppercase channel (in-memory) and print the messages. - **/ - @Incoming("uppercase") - public void sink(String word) { - System.out.println(">> " + word); - } -} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/SomePage.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/SomePage.java deleted file mode 100644 index b94c9bcdbf..0000000000 --- a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/SomePage.java +++ /dev/null @@ -1,29 +0,0 @@ -package tech.ebp.oqm.plugin.imageSearch; - -import io.quarkus.qute.Template; -import io.quarkus.qute.TemplateInstance; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.MediaType; - -import static java.util.Objects.requireNonNull; - -@Path("/some-page") -public class SomePage { - - private final Template page; - - public SomePage(Template page) { - this.page = requireNonNull(page, "page is required"); - } - - @GET - @Produces(MediaType.TEXT_HTML) - public TemplateInstance get(@QueryParam("name") String name) { - return page.data("name", name); - } - -} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingConfig.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/config/GreetingConfig.java similarity index 80% rename from software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingConfig.java rename to software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/config/GreetingConfig.java index 279ea03853..d799016434 100644 --- a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/GreetingConfig.java +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/config/GreetingConfig.java @@ -1,4 +1,4 @@ -package tech.ebp.oqm.plugin.imageSearch; +package tech.ebp.oqm.plugin.imageSearch.config; import io.smallrye.config.ConfigMapping; import io.smallrye.config.WithName; diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpoint.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpoint.java new file mode 100644 index 0000000000..ee8d72f6ae --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpoint.java @@ -0,0 +1,36 @@ +package tech.ebp.oqm.plugin.imageSearch.interfaces; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import tech.ebp.oqm.plugin.imageSearch.model.search.ImageSearch; +import tech.ebp.oqm.plugin.imageSearch.model.search.SearchResults; +import tech.ebp.oqm.plugin.imageSearch.service.ImageSearchService; + +import java.io.IOException; +import java.util.TreeMap; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +@RequestScoped +@Path("/api/v1/{oqmDbIdOrName}/imageSearch") +public class ImageSearchEndpoint { + + @Inject + ImageSearchService imageSearchService; + + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(APPLICATION_JSON) + public TreeMap search( + @BeanParam ImageSearch query + ) throws IOException { + return this.imageSearchService.search(query); + } +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/event/OqmCoreEventsHandler.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/event/OqmCoreEventsHandler.java new file mode 100644 index 0000000000..ef1111d576 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/interfaces/event/OqmCoreEventsHandler.java @@ -0,0 +1,20 @@ +package tech.ebp.oqm.plugin.imageSearch.interfaces.event; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.reactive.messaging.*; + +import jakarta.enterprise.context.ApplicationScoped; + +@Slf4j +@ApplicationScoped +public class OqmCoreEventsHandler { + + /** + * Consume the message from the OQM Core api Events Channel. + * TODO:: tie in with various downstream services + **/ + @Incoming("oqm-events") + public void toUpperCase(String message) { + log.info("Received message: {}", message); + } +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/resnet/ImageVector.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/resnet/ImageVector.java new file mode 100644 index 0000000000..4ff1323927 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/resnet/ImageVector.java @@ -0,0 +1,27 @@ +package tech.ebp.oqm.plugin.imageSearch.model.resnet; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import org.bson.types.ObjectId; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ImageVector { + + private ObjectId id; + + @NonNull + private String oqmDb; + + @NonNull + private String imageId; + + private int imageRevision; + + private float[] vector; +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/ImageSearch.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/ImageSearch.java new file mode 100644 index 0000000000..cdb0e6a1ee --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/ImageSearch.java @@ -0,0 +1,42 @@ +package tech.ebp.oqm.plugin.imageSearch.model.search; + + +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.core.MediaType; +import lombok.Builder; +import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; +import org.jboss.resteasy.reactive.PartType; + +import java.io.InputStream; + +@Builder +public class ImageSearch { + + @Parameter(description = "The database we are concerning ourselves with.") + @PathParam("oqmDbIdOrName") + public String oqmDbIdOrName; + + @Parameter(description = "The file content to upload") + @FormParam("file") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + public InputStream file; + + @Parameter(description = "The name of the file") + @FormParam("fileName") + @PartType(MediaType.TEXT_PLAIN) + public String fileName; + + @Parameter(description = "The max number of results to return.") + @FormParam("maxResults") + @DefaultValue("10") + @PartType(MediaType.TEXT_PLAIN) + public Integer maxResults; + + @Parameter(description = "The threshold of how similar to identify with.") + @FormParam("maxResults") + @DefaultValue("75.0") + @PartType(MediaType.TEXT_PLAIN) + public Double threshold; +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResult.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResult.java new file mode 100644 index 0000000000..e4c70abe33 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResult.java @@ -0,0 +1,17 @@ +package tech.ebp.oqm.plugin.imageSearch.model.search; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SearchResult { + private String item; + private String image; + +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResults.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResults.java new file mode 100644 index 0000000000..a27cbd5779 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/model/search/SearchResults.java @@ -0,0 +1,20 @@ +package tech.ebp.oqm.plugin.imageSearch.model.search; + +import io.quarkus.arc.All; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.TreeMap; +import java.util.TreeSet; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SearchResults { + + TreeMap results; +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageData.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageData.java new file mode 100644 index 0000000000..ea570e1bf9 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageData.java @@ -0,0 +1,42 @@ +package tech.ebp.oqm.plugin.imageSearch.service; + +//Stores full filepath to the image, name of the image, and the feature vector data +//Easily modifiable to include other image metadata +public class ImageData { + String filepath; + String filename; + float[] imageFeatureVector; + + //This default constructor is necessary for these objects to be serialized into JSON files + public ImageData(){} + + public ImageData(String filepath, String filename, float[] imageFeatureVector) { + this.filepath = filepath; + this.filename = filename; + this.imageFeatureVector = imageFeatureVector; + } + + public String getFilepath(){ + return this.filepath; + } + + public void setFilepath(String filepath){ + this.filepath = filepath; + } + + public String getFilename(){ + return this.filename; + } + + public void setFilename(String filename){ + this.filename = filename; + } + + public float[] getImageFeatureVector(){ + return this.imageFeatureVector; + } + + public void setImageFeatureVector(float[] imageFeatureVector){ + this.imageFeatureVector = imageFeatureVector; + } +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchService.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchService.java new file mode 100644 index 0000000000..2ce480691b --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchService.java @@ -0,0 +1,204 @@ +package tech.ebp.oqm.plugin.imageSearch.service; + +import io.micrometer.core.instrument.MeterRegistry; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import jakarta.enterprise.context.ApplicationScoped; + +import java.io.InputStream; +import java.net.URL; + +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import nu.pattern.OpenCV; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.opencv.core.*; +import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; +import org.tensorflow.Result; +import org.tensorflow.Tensor; +import org.tensorflow.SavedModelBundle; +import org.tensorflow.ndarray.FloatNdArray; +import org.tensorflow.ndarray.StdArrays; +import org.tensorflow.types.TFloat32; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.OqmCoreApiClientService; +import tech.ebp.oqm.plugin.imageSearch.interfaces.ImageSearchEndpoint; +import tech.ebp.oqm.plugin.imageSearch.model.resnet.ImageVector; +import tech.ebp.oqm.plugin.imageSearch.model.search.ImageSearch; +import tech.ebp.oqm.plugin.imageSearch.model.search.SearchResults; +import tech.ebp.oqm.plugin.imageSearch.service.mongo.ResnetVectorService; + +@Slf4j +@ApplicationScoped +public class ImageSearchService { + + private static final String RESNET_V2_MODEL_PATH = "models/resnetV2"; + public static final String inputTensorName = "serving_default_inputs"; + public static final String outputTensorName = "StatefulPartitionedCall"; + private static final URL dir = ImageSearchService.class.getClassLoader().getResource(RESNET_V2_MODEL_PATH); + public static final Size modelImageSize = new Size(500, 500); + //likely move to instance + public static final SavedModelBundle model; + + static { + log.info("Loading OpenCV"); + OpenCV.loadLocally(); + log.info("OpenCV loaded"); + log.info("Loading ResNet Model: {}", dir); + log.debug("Passing in: {}", dir.getFile()); + model = SavedModelBundle.load(dir.getFile()); + //model = SavedModelBundle.load(dir.getFile().substring(1)); //Don't commit + } + + + @RestClient + OqmCoreApiClientService coreApiClient; + + @Inject + ResnetVectorService resnetVectorService; + + @Inject + MeterRegistry registry; + + /** + * + * @param query + * @return + */ + @WithSpan + public TreeMap search(ImageSearch query) throws IOException { + log.info("Searching for query: {}", query.fileName); + + TreeMap tree = this.getSimilarities(query.oqmDbIdOrName, query.file); + int tmpIter = 0; + for (Map.Entry entry : tree.entrySet()) { + log.info("Filename: {}, Score: {}", entry.getValue(), entry.getKey()); + tmpIter++; + if (tmpIter > query.maxResults) { + break; + } + } + + return tree; + } + + /** + * Method for generating a feature vector from image data. + *

+ * TODO:: validate/ integrate with rest + * + * @param imageBytes The bytes of image data + * + * @return The processes image feature vector + */ + @WithSpan + public float[] generateImageFeatureVector(byte[] imageBytes) { + //TODO:: need to release all `Mat` objects + // Release temporary buffer. + try ( + Tensor inputTensor = preprocessImage(imageBytes); + Result outputTensor = model.session().runner() + .feed(inputTensorName, inputTensor) + .fetch(outputTensorName) + .run() + ) { + return StdArrays.array2dCopyOf((FloatNdArray) outputTensor.get(0))[0]; + } catch(Exception e) { + e.printStackTrace(); //better logging and exception + return null; + } + } + + public float[] generateImageFeatureVector(InputStream imageStream) throws IOException { + return this.generateImageFeatureVector(imageStream.readAllBytes()); + } + + /** + Converts image file to matrix, resize, normalize values, + convert to tensor type to prepare image for tensorflow model + */ + public static Tensor preprocessImage(byte[] imageBytes) { + MatOfByte matOfBytes= new MatOfByte(imageBytes); + Mat mat = Imgcodecs.imdecode(matOfBytes, Imgcodecs.IMREAD_UNCHANGED); + + //TODO:: Mat handling and buffers empty and release + if (mat.empty()) {//invalid image / unable to process image + throw new RuntimeException("Failed to decode image!"); + } + Imgproc.resize(mat, mat, modelImageSize); + mat.convertTo(mat, CvType.CV_32FC3); + Core.normalize(mat, mat, 0, 1, Core.NORM_MINMAX); + float[] imageData = new float[(int) (mat.total() * mat.channels())]; + mat.get(0, 0, imageData); + int heightVal = (int) modelImageSize.height; + int widthVal = (int) modelImageSize.width; + + int oldIter = 0; + float[][][][] newImageData = new float[1][heightVal][widthVal][3]; + for (int i = 0; i < heightVal; i++) { + for (int j = 0; j < widthVal; j++) { + for (int k = 0; k < 3; k++) { + newImageData[0][i][j][k] = imageData[oldIter]; + oldIter++; + } + } + } + return TFloat32.tensorOf(StdArrays.ndCopyOf(newImageData)); + } + + /** + Takes in the ImageData object prepared from the query image + Runs the cosineSimilarity function on the query feature vector against + every image present in the previously generated jsonData + Returns a reverse sorted TreeMap containing the similarity score and image filename + */ + @WithSpan + public TreeMap getSimilarities(String oqmDbIdOrName, InputStream queryImage) throws IOException { + log.info("Getting similarities for query"); + float[] queryFeatures = this.generateImageFeatureVector(queryImage); + TreeMap similarityMap = new TreeMap<>(Collections.reverseOrder()); + + long numComparisons = 0; + for (Iterator it = resnetVectorService.getAllVectors(oqmDbIdOrName); it.hasNext();) { + ImageVector curData = it.next(); + numComparisons++; + log.trace("Processing image comparison with image: {}", curData.getImageId()); + double simScore = cosineSimilarity(queryFeatures, curData.getVector()); + similarityMap.put(simScore, curData.getImageId()); + log.trace("Done processing image comparison with image: {}", curData.getImageId()); + } + + log.info("Done getting similarities for query. Comparisons: {}", numComparisons); + return similarityMap; + } + + /** + Converts the two feature vector arrays to Mat, performs cosine similarity + Returns a similarity score between 0 and 1 + */ + private static double cosineSimilarity(float[] img1, float[] img2) { + Mat img1Mat = convertFloatArrtoMat(img1); + Mat img2Mat = convertFloatArrtoMat(img2); + double dotProd = img1Mat.dot(img2Mat); + double mag1 = Core.norm(img1Mat, Core.NORM_L2); + double mag2 = Core.norm(img2Mat, Core.NORM_L2); + if (mag1 != 0.0 && mag2 != 0.0) { + return ((dotProd / (mag1 * mag2)) + 1.0) / 2.0; + } + return 0.0; + } + + /** + Converts the float[] type image feature vectors to type Mat from the OpenCV + library, easier and more efficient math + */ + private static Mat convertFloatArrtoMat(float[] arr) { + Mat newMat = new Mat(1, arr.length, CvType.CV_32F); + newMat.put(0, 0, arr); + return newMat; + } +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorService.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorService.java new file mode 100644 index 0000000000..fa3b7daf06 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorService.java @@ -0,0 +1,186 @@ +package tech.ebp.oqm.plugin.imageSearch.service.mongo; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import io.opentelemetry.instrumentation.annotations.WithSpan; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.OqmCoreApiClientService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.searchObjects.ImageSearch; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.sso.KcClientAuthService; +import tech.ebp.oqm.plugin.imageSearch.interfaces.ImageSearchEndpoint; +import tech.ebp.oqm.plugin.imageSearch.model.resnet.ImageVector; + +import tech.ebp.oqm.plugin.imageSearch.service.ImageSearchService; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +import static com.mongodb.client.model.Filters.and; + +@Slf4j +@ApplicationScoped +public class ResnetVectorService { + + @Inject + @Getter(AccessLevel.PROTECTED) + MongoClient mongoClient; + + @Getter + @ConfigProperty(name = "quarkus.mongodb.database") + String database; + + @RestClient + OqmCoreApiClientService oqmCoreApiClientService; + + @Inject + KcClientAuthService serviceAccountService; + + @Inject + ImageSearchService imageSearchService; + + + protected MongoDatabase getMongoDatabase() { + return this.getMongoClient().getDatabase(this.getDatabase()); + } + + public MongoCollection getTypedCollection() { + return this.getMongoDatabase().getCollection("resnet-image-vectors", ImageVector.class); + } + + public long getNumVectors(String database) { + return this.getTypedCollection().countDocuments(Filters.eq("oqmDb", database)); + } + + public Iterator getAllVectors(String database) { + return this.getTypedCollection().find(Filters.eq("oqmDb", database)).iterator(); + + //example + // and( + // Filters.eq("imageId", id), + // Filters.eq("database", database) + // ) + } + + public Iterator getAllVectors(){ + return this.getTypedCollection().find().iterator(); + } + + private void processImage(String database, String imageId, int imageRevision) { + log.info("Processing image revision: {}, revision: {}", imageId, imageRevision); + + //TODO:: check if already processed, skip if exists Done + //think this is good + MongoCollection collection = this.getTypedCollection(); + if ( + collection.find( + Filters.and( + Filters.eq("imageId", imageId), + Filters.eq("revision", imageRevision) + ) + ).first() != null + ) { + return; + } + + try ( + InputStream is = this.oqmCoreApiClientService.imageGetRevisionData(this.serviceAccountService.getAuthString(), database, imageId, imageRevision + "") + .await() + .indefinitely() + ) { + ImageVector.ImageVectorBuilder builder = ImageVector.builder(); + + builder.oqmDb(database); + builder.imageId(imageId); + builder.imageRevision(imageRevision); + builder.vector(this.imageSearchService.generateImageFeatureVector(is)); + + this.getTypedCollection().insertOne(builder.build()); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + private void processImage(String oqmDatabase, ObjectNode imageMetadata) { + String imageId = imageMetadata.get("id").asText(); + int numRevisions = imageMetadata.get("numRevisions").asInt(); + + log.info("Processing image: {}, # revisions: {}", imageId, numRevisions); + + for (int i = 1; i <= numRevisions; i++) { + this.processImage(oqmDatabase, imageId, i); + } + } + + //Iterates db, operate on each image in turn + @WithSpan + public void initVectors() { + log.info("Processing images in all databases."); + ImageSearch imageSearch = ImageSearch.builder() + .pageNum(1) + .pageSize(100) + .build(); + + ArrayNode dbList = this.oqmCoreApiClientService.manageDbList(this.serviceAccountService.getAuthString()).await().indefinitely(); + for (JsonNode db : dbList) { + + String curOqmDb = db.get("name").asText(); //circle back to + ObjectNode results; + + log.info("Processing images database: {}", curOqmDb); + + try { + do { + results = this.oqmCoreApiClientService.imageSearch(this.serviceAccountService.getAuthString(), curOqmDb, imageSearch).await().indefinitely(); + + log.debug("Retrieved new page of results"); + + for (JsonNode curImageResult : results.get("results")) { + this.processImage(curOqmDb, (ObjectNode) curImageResult); + } + + imageSearch.setPageNum(imageSearch.getPageNum() + 1); + } while (!results.get("pagingCalculations").get("onLastPage").asBoolean()); + + } catch(Exception e) { + log.error("Error processing images database: {}", curOqmDb, e); + throw new RuntimeException(e); + } + } + + // TODO:: remove vectors not in oqm core db + Iterator allVec = this.getAllVectors(); + while (allVec.hasNext()) { + ImageVector imageVector = allVec.next(); + String imageID = imageVector.getImageId(); + String db = imageVector.getOqmDb(); + // TODO:: check which error is being thrown + ObjectNode imageData = this.oqmCoreApiClientService + .imageGet( + this.serviceAccountService.getAuthString(), db, imageID) + .onFailure() + .recoverWithNull() + .await().indefinitely(); + if(imageData == null) { + this.getTypedCollection().deleteOne(Filters.eq("_id", imageVector.getId())); + } + + } + + } + + public void deleteAll(){ + this.getTypedCollection().deleteMany(Filters.empty()); + } +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/CustomCodecProvider.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/CustomCodecProvider.java new file mode 100644 index 0000000000..d29bc0f5c5 --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/CustomCodecProvider.java @@ -0,0 +1,31 @@ +package tech.ebp.oqm.plugin.imageSearch.service.mongo.codecs; + + +import jakarta.enterprise.context.ApplicationScoped; +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; + +import java.util.List; + +@ApplicationScoped +public class CustomCodecProvider implements CodecProvider { + + private final List> codecs = List.of( + new FloatArrayCodec() + ); + + @Override + public Codec get(Class clazz, CodecRegistry registry) { + //noinspection rawtypes + for (Codec codec : this.codecs) { + if (clazz == codec.getEncoderClass()) { + //noinspection unchecked + return (Codec) codec; + } + } + return null; + } + + +} diff --git a/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/FloatArrayCodec.java b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/FloatArrayCodec.java new file mode 100644 index 0000000000..e14d1bd89d --- /dev/null +++ b/software/plugins/image-search/src/main/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/codecs/FloatArrayCodec.java @@ -0,0 +1,47 @@ +package tech.ebp.oqm.plugin.imageSearch.service.mongo.codecs; + +import org.bson.BsonReader; +import org.bson.BsonType; +import org.bson.BsonWriter; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; + +import java.util.ArrayList; +import java.util.List; + +public class FloatArrayCodec implements Codec { + + @Override + public void encode(BsonWriter writer, float[] value, EncoderContext encoderContext) { + writer.writeStartArray(); + for (float f : value) { + // MongoDB stores all numbers as doubles in BSON + writer.writeDouble(f); + } + writer.writeEndArray(); + } + + @Override + public float[] decode(BsonReader reader, DecoderContext decoderContext) { + reader.readStartArray(); + List list = new ArrayList<>(); + while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { + // Read as double and cast back to float, accepting potential precision loss + list.add((float) reader.readDouble()); + } + reader.readEndArray(); + + // Convert list back to native float array + float[] array = new float[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + @Override + public Class getEncoderClass() { + return float[].class; + } +} \ No newline at end of file diff --git a/software/plugins/image-search/src/main/resources/application.yml b/software/plugins/image-search/src/main/resources/application.yml index bee8d6192a..439aa3c428 100644 --- a/software/plugins/image-search/src/main/resources/application.yml +++ b/software/plugins/image-search/src/main/resources/application.yml @@ -2,12 +2,44 @@ greeting: message: "hello" mp: messaging: - outgoing: - words-out: - topic: "words" incoming: - words-in: - auto: - offset: - reset: "earliest" - topic: "words" + oqm-events: + topic: "oqm-core-all-events" +# auto: +# offset: +# reset: "earliest" + +quarkus: + log: + level: "INFO" + mongodb: + database: "oqm-image-search" + tracing: + enabled: true + keycloak: + devservices: + port: 9328 + realm-path: "dev/oqm-realm.json" + realm-name: oqm + kafka: + devservices: + enabled: true + port: 9192 + otel: + metrics: + enabled: true + traces: + enabled: true + logs: + enabled: true + +"%dev": + quarkus: + http: + port: 8080 + ssl-port: 8443 + oidc: + # TODO:: remove when https://github.com/quarkusio/quarkus/issues/47581 + client-id: oqm-app + credentials: + secret: "**********" diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/BasicSetupTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/BasicSetupTest.java new file mode 100644 index 0000000000..d2594949b0 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/BasicSetupTest.java @@ -0,0 +1,119 @@ +package tech.ebp.oqm.plugin.imageSearch; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.dev.CoreApiDevDbManagementService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.OqmCoreApiClientService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.files.FileUploadBody; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.sso.KcClientAuthService; +import tech.ebp.oqm.plugin.imageSearch.testResources.testClasses.RunningServerTest; +//import tech.ebp.oqm.plugin.imageSearch.service.mongo.ResnetVectorService; + +import java.io.*; +import java.net.URISyntaxException; + +@Disabled +@Slf4j +@QuarkusTest +public class BasicSetupTest extends RunningServerTest { + + @RestClient + OqmCoreApiClientService oqmCoreApiClientService; + + @Inject + KcClientAuthService serviceAccountService; + + @Inject + CoreApiDevDbManagementService devDbManagementService; + + @Test + public void testBasicSetup() throws IOException, URISyntaxException { + + //Parses testImages folder and gets a list of filenames + //File directory = new File((Objects.requireNonNull(BasicSetupTest.class.getClassLoader().getResource("dev/testImages"))).toURI()); + File directory = new File("dev/testImages"); + + File[] fileList = directory.listFiles(); + Assertions.assertNotNull(fileList); + String[] imageList = new String[fileList.length]; + for(int i = 0; i < fileList.length; i++){ + imageList[i] = fileList[i].getName(); + } + + //Makes an item for each image in testImages folder + for(int i = 0; i < imageList.length; i++){ + uploadSingleImage(imageList[i], "test" + (i + 1) + ".jpg"); + } + + } + + //takes in the name of an image in the temporary testImages folder + // + public void uploadSingleImage(String resourceLocation, String outputFilename) throws IOException { + this.setupOqmDb(TEST_DB); + // ... init vectors + + + //uploads image object and generates image id + String imageId; + try( + //InputStream inputStream = BasicSetupTest.class.getClassLoader().getResourceAsStream("dev/testImages/" + resourceLocation); + InputStream inputStream = new FileInputStream("dev/testImages/" + resourceLocation); + ){ + FileUploadBody testImageUploadObj = FileUploadBody.builder() + .file(inputStream) + .fileName(resourceLocation) + .description(FAKER.lorem().sentence()) + .source(resourceLocation) + .build(); + imageId = this.oqmCoreApiClientService.imageAdd( + this.serviceAccountService.getAuthString(), + "default", + testImageUploadObj + ).subscribeAsCompletionStage().join().get("id").asText(); + } + + log.info("imageId: {}", imageId); + + //create an item object for the image + ObjectNode item = JsonNodeFactory.instance.objectNode(); + item.put("name", FAKER.appliance().equipment() + "-" + outputFilename); + item.put("storageType", "BULK"); + item.putObject("unit").put("string", "units"); + item.putArray("imageIds").add(imageId); + + //make item id + ObjectNode newItem = this.oqmCoreApiClientService.invItemCreate(serviceAccountService.getAuthString(), "default", item).subscribeAsCompletionStage().join(); + log.info("item: {}", newItem); + + //check to make sure image object is good + ObjectNode imageObj = this.oqmCoreApiClientService.imageGet(serviceAccountService.getAuthString(), "default", imageId).subscribeAsCompletionStage().join(); + log.info("imageObj: {}", imageObj); + + + //Get image object data, don't fully understand this part + InputStream response = this.oqmCoreApiClientService.imageGetRevisionData( + this.serviceAccountService.getAuthString(), + "default", + imageId, + "latest" + ).await().indefinitely(); + log.info("Response: {}", response); + + //Pull the image data back down into a test image in a new folder + try( + OutputStream outputStream = new FileOutputStream("build/test-results/" + outputFilename); + ){ + response.transferTo(outputStream); + } + + } + + +} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/GreetingResourceTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/GreetingResourceTest.java deleted file mode 100644 index 5e55a381aa..0000000000 --- a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/GreetingResourceTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package tech.ebp.oqm.plugin.imageSearch; - -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -@QuarkusTest -class GreetingResourceTest { - @Test - void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("Hello from Quarkus REST")); - } - -} \ No newline at end of file diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplicationTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplicationTest.java deleted file mode 100644 index 6184879fdf..0000000000 --- a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/MyMessagingApplicationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package tech.ebp.oqm.plugin.imageSearch; - -import io.quarkus.test.junit.QuarkusTest; - -import org.eclipse.microprofile.reactive.messaging.Message; -import org.junit.jupiter.api.Test; - -import jakarta.inject.Inject; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@QuarkusTest -class MyMessagingApplicationTest { - - @Inject - MyMessagingApplication application; - - @Test - void test() { - assertEquals("HELLO", application.toUpperCase(Message.of("Hello")).getPayload()); - assertEquals("BONJOUR", application.toUpperCase(Message.of("bonjour")).getPayload()); - } -} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpointTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpointTest.java new file mode 100644 index 0000000000..2a72825023 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/interfaces/ImageSearchEndpointTest.java @@ -0,0 +1,14 @@ +package tech.ebp.oqm.plugin.imageSearch.interfaces; + +import io.quarkus.test.junit.QuarkusTest; +import lombok.extern.slf4j.Slf4j; +import tech.ebp.oqm.plugin.imageSearch.testResources.testClasses.RunningServerTest; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@QuarkusTest +class ImageSearchEndpointTest extends RunningServerTest { + + //TODO:: this +} \ No newline at end of file diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchServiceTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchServiceTest.java new file mode 100644 index 0000000000..3e7cae770a --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/ImageSearchServiceTest.java @@ -0,0 +1,55 @@ +package tech.ebp.oqm.plugin.imageSearch.service; + +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.files.FileUploadBody; +import tech.ebp.oqm.plugin.imageSearch.model.search.ImageSearch; +import tech.ebp.oqm.plugin.imageSearch.service.mongo.ResnetVectorService; +import tech.ebp.oqm.plugin.imageSearch.testResources.testClasses.RunningServerTest; + + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.TreeMap; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@QuarkusTest +class ImageSearchServiceTest extends RunningServerTest { + + @Inject + ImageSearchService imageSearchService; + + @Inject + ResnetVectorService resnetVectorService; + + @Test + public void testBasicSearch() throws IOException { + this.setupOqmDb(TEST_DB); + log.info("Testing initDb"); + + this.resnetVectorService.initVectors(); + + log.info("Finished initDb"); + + try (Stream stream = Files.list(Paths.get(TEST_IMG_DIR)); InputStream is = Files.newInputStream(stream.findFirst().get());) { + TreeMap results = this.imageSearchService.search( + ImageSearch.builder() + .oqmDbIdOrName(TEST_DB) + .file(is) + .fileName("foo.png") + .maxResults(10) + .threshold(0.75) + .build() + ); + log.info("Found results: {}", results); + } + } +} \ No newline at end of file diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorServiceTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorServiceTest.java new file mode 100644 index 0000000000..3f9c2d10f0 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/service/mongo/ResnetVectorServiceTest.java @@ -0,0 +1,105 @@ +package tech.ebp.oqm.plugin.imageSearch.service.mongo; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.mongodb.client.model.Filters; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.files.FileUploadBody; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.searchObjects.ImageSearch; +import tech.ebp.oqm.plugin.imageSearch.model.resnet.ImageVector; +import tech.ebp.oqm.plugin.imageSearch.testResources.testClasses.RunningServerTest; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@QuarkusTest +class ResnetVectorServiceTest extends RunningServerTest { + + @Inject + ResnetVectorService resnetVectorService; + + @Test + public void testInitDb() { + this.setupOqmDb(TEST_DB); + log.info("Testing initDb"); + + this.resnetVectorService.initVectors(); + + log.info("Finished initDb"); + + + ObjectNode imageSearch = this.getOqmCoreApiClientService().imageSearch( + this.getServiceAccountService().getAuthString(), + TEST_DB, + ImageSearch.builder().build() + ).await().indefinitely(); + + assertEquals( + imageSearch.get("numResultsForEntireQuery").asInt(), + this.resnetVectorService.getNumVectors(TEST_DB) + ); + + + for (Iterator it = this.resnetVectorService.getAllVectors(TEST_DB); it.hasNext(); ) { + ImageVector imageVector = it.next(); + + assertNotNull(imageVector.getImageId()); + assertNotNull(imageVector.getVector()); + + this.getOqmCoreApiClientService().imageGet(//if no exception, then it exists + this.getServiceAccountService().getAuthString(), + TEST_DB, + imageVector.getImageId() + ).await().indefinitely(); + } + } + @Test + public void testInitDBRemoveDeletedVector() throws IOException { + this.setupOqmDb(TEST_DB); + ObjectNode image; +// try (Stream stream = Files.list(Paths.get(TEST_IMG_DIR))) { +// List files = stream +// .filter(Files::isRegularFile) +// .collect(Collectors.toList()); + ObjectId id = this.resnetVectorService.getTypedCollection().insertOne( + ImageVector.builder() + .oqmDb(TEST_DB) + .imageId("foo") + .vector(new float[0]) + .imageRevision(1) + .build()).getInsertedId().asObjectId().getValue(); + //log.debug("Added image: {}", image); + //log.info("Testing initDb"); + this.resnetVectorService.initVectors(); + + //TODO:: Check if id is present or not after initVectors allegedly deletes + assertEquals(0, this.resnetVectorService.getTypedCollection().countDocuments(Filters.eq("_id", id))); + //log.info("Finished initDb"); + } + + @AfterEach + public void clearVectors( + TestInfo testInfo + ) { + log.info("Running after method for test {}", testInfo.getDisplayName()); + + this.resnetVectorService.deleteAll(); + + log.info("Completed after step."); + } +} \ No newline at end of file diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/RunningServerTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/RunningServerTest.java new file mode 100644 index 0000000000..c3adefef02 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/RunningServerTest.java @@ -0,0 +1,151 @@ +package tech.ebp.oqm.plugin.imageSearch.testResources.testClasses; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.OqmCoreApiClientService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.files.FileUploadBody; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.sso.KcClientAuthService; +import tech.ebp.oqm.plugin.imageSearch.testResources.testUsers.TestUserService; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.restassured.RestAssured.given; + + +@Slf4j +@Execution(ExecutionMode.SAME_THREAD) +public abstract class RunningServerTest extends WebServerTest { + + public static final String TEST_DB = "default"; //TODO:: instead of using this, get actual id from db + public static final String TEST_IMG_DIR = "./dev/testImages/"; + + @Getter + @RestClient + OqmCoreApiClientService oqmCoreApiClientService; + + @Getter + @Inject + KcClientAuthService serviceAccountService; + + @Getter + @ConfigProperty(name = "oqm.core.api.baseUri") + String coreApiBaseUri; + + + @Getter + private final TestUserService testUserService = TestUserService.getInstance(); + + @Inject + ObjectMapper objectMapper; + + @BeforeEach + public void beforeEach(TestInfo testInfo) { + log.info("Before test " + testInfo.getTestMethod().get().getName()); + } + + @AfterEach + public void afterEach( + TestInfo testInfo + ) { + log.info("Running after method for test {}", testInfo.getDisplayName()); + + this.oqmCoreApiClientService.manageDbClearAll(this.serviceAccountService.getAuthString()).await().indefinitely(); + + log.info("Completed after step."); + } + + + protected void setupOqmDb(String dbName) { + //TODO:: setup core api database with images, items, etc + log.info("Setting up OQM Core API database with test images."); + try (Stream stream = Files.list(Paths.get(TEST_IMG_DIR))) { + List files = stream + .filter(Files::isRegularFile) + .collect(Collectors.toList()); + + for (Path path : files) { + log.info("Adding Item/ file: {}", path.getFileName()); + String fileName = path.getFileName().toString(); + String itemName = fileName.toLowerCase() + .substring(0, fileName.lastIndexOf('.')) + .replaceAll("_", " ") + .strip(); + + ObjectNode image; + + try ( + InputStream is = Files.newInputStream(path); + ) { + image = this.oqmCoreApiClientService.imageAdd( + this.serviceAccountService.getAuthString(), + dbName, + FileUploadBody.builder() + .fileName(fileName) + .file(is) + .description("Test Image") + .source("testFiles") + .build() + ).await().indefinitely(); + } + log.debug("Added image: {}", image); + + + ObjectNode curItem = objectMapper.createObjectNode() + .put("name", itemName) + .put("storageType", "BULK"); + curItem.putArray("imageIds").add(image.get("id").asText()); + curItem.putObject("unit").put("string", "units"); + + curItem = this.oqmCoreApiClientService.invItemCreate( + this.serviceAccountService.getAuthString(), + dbName, + curItem + ).await().indefinitely(); + + log.debug("Added item: {}", curItem); + + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + log.info("Completed setting up OQM Core API database with test images."); + } + + public ObjectNode testGetTestImage() throws IOException { + try ( + Stream stream = Files.list(Paths.get(TEST_IMG_DIR)); + InputStream is = Files.newInputStream(stream.findFirst().get()); + ) { + return this.getOqmCoreApiClientService().imageAdd( + this.getServiceAccountService().getAuthString(), + TEST_DB, + FileUploadBody.builder() + .fileName("testFoo.jpg") + .file(is) + .description("Test Image") + .source("testFiles") + .build() + ).await().indefinitely(); + } + } + +} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/WebServerTest.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/WebServerTest.java new file mode 100644 index 0000000000..821373ac32 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testClasses/WebServerTest.java @@ -0,0 +1,10 @@ +package tech.ebp.oqm.plugin.imageSearch.testResources.testClasses; + +import net.datafaker.Faker; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +@Execution(ExecutionMode.CONCURRENT) +public abstract class WebServerTest { + public static final Faker FAKER = new Faker(); +} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUser.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUser.java new file mode 100644 index 0000000000..7ed8d9799d --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUser.java @@ -0,0 +1,31 @@ +package tech.ebp.oqm.plugin.imageSearch.testResources.testUsers; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class TestUser { + + private String firstname; + private String lastname; + private String username; + @ToString.Exclude + private String email; + @ToString.Exclude + private String password; + @ToString.Exclude + private String jwt; + + public String getUsername(){ + if(username == null){ + return this.firstname + "." + this.lastname; + } + return this.username; + } +} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserService.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserService.java new file mode 100644 index 0000000000..8e64194560 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserService.java @@ -0,0 +1,96 @@ +package tech.ebp.oqm.plugin.imageSearch.testResources.testUsers; + +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.datafaker.Faker; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * { + * "exp": 1706447044, + * "iat": 1706445545, + * "auth_time": 1706445544, + * "jti": "0c2d411d-1012-499e-a548-6919f384084a", + * "iss": "http://oqm-dev.local:8115/realms/oqm", + * "aud": "oqm-base-station", + * "sub": "575eb08f-7a8a-41cc-ac87-c41f84e03c84", + * "typ": "ID", + * "azp": "oqm-base-station", + * "session_state": "cfa63d15-f520-4e90-a204-9cafb5cc5621", + * "at_hash": "ry6laHfVyN7hlYTBzpmTAA", + * "acr": "1", + * "sid": "cfa63d15-f520-4e90-a204-9cafb5cc5621", + * "upn": "snappawapa", + * "email_verified": false, + * "name": "Greg Stewart", + * "groups": [ + * "default-roles-oqm", + * "inventoryView", + * "offline_access", + * "itemCheckout", + * "inventoryEdit", + * "uma_authorization", + * "inventoryAdmin", + * "user" + * ], + * "preferred_username": "snappawapa", + * "given_name": "Greg", + * "family_name": "Stewart", + * "email": "contact@gjstewart.net" + * } + */ +@Slf4j +@NoArgsConstructor +public class TestUserService { + private final static Faker FAKER = new Faker(); + + private static String getRandomPassword() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 16; i += 4) { + sb.append(RandomStringUtils.random(1, "abcdefg")); + sb.append(RandomStringUtils.random(1, "ABCDEFG")); + sb.append(RandomStringUtils.random(1, "1234567")); + sb.append(RandomStringUtils.random(1, "!@#$%^&")); + } + return sb.toString(); + } + + private final static TestUserService INSTANCE = new TestUserService(); + + public static TestUserService getInstance() { + return INSTANCE; + } + + private Map testUsers = new HashMap<>(); + + public TestUser getTestUser(TestUserType type) { + if (!this.testUsers.containsKey(type)) { + testUsers.put( + type, + TestUser.builder() + .email(FAKER.internet().emailAddress()) + .firstname(FAKER.name().firstName()) + .lastname(FAKER.name().lastName()) + .password(getRandomPassword()) + .build() + ); + } + + return this.testUsers.get(type); + } + + public TestUser getTestUser() { + return this.getTestUser(TestUserType.REGULAR); + } + + public List getAllTestUsers(){ + return Arrays.stream(TestUserType.values()) + .map(this::getTestUser) + .toList(); + } +} diff --git a/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserType.java b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserType.java new file mode 100644 index 0000000000..ffacfbadb4 --- /dev/null +++ b/software/plugins/image-search/src/test/java/tech/ebp/oqm/plugin/imageSearch/testResources/testUsers/TestUserType.java @@ -0,0 +1,6 @@ +package tech.ebp.oqm.plugin.imageSearch.testResources.testUsers; + +public enum TestUserType { + REGULAR, + ADMIN +} diff --git a/software/plugins/image-search/src/test/resources/application.yaml b/software/plugins/image-search/src/test/resources/application.yaml new file mode 100644 index 0000000000..8346f107b5 --- /dev/null +++ b/software/plugins/image-search/src/test/resources/application.yaml @@ -0,0 +1,10 @@ + + +quarkus: + oidc: + client-id: oqm-app + credentials: + secret: "**********" + keycloak: + devservices: + realm-path: "dev/oqm-realm.json" \ No newline at end of file diff --git a/software/plugins/image-search/src/test/resources/testImages/FAIESSGRE.jpg b/software/plugins/image-search/src/test/resources/testImages/FAIESSGRE.jpg new file mode 100644 index 0000000000..12aae1eedb Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/FAIESSGRE.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/Pliers.jpg b/software/plugins/image-search/src/test/resources/testImages/Pliers.jpg new file mode 100644 index 0000000000..c49d43fbe7 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/Pliers.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/Screw.jpg b/software/plugins/image-search/src/test/resources/testImages/Screw.jpg new file mode 100644 index 0000000000..596092fd92 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/Screw.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/Screw2.jpg b/software/plugins/image-search/src/test/resources/testImages/Screw2.jpg new file mode 100644 index 0000000000..a3ee2bd2b1 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/Screw2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/Shovel.jpg b/software/plugins/image-search/src/test/resources/testImages/Shovel.jpg new file mode 100644 index 0000000000..7a506ae5b8 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/Shovel.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/Wirestrippers.jpg b/software/plugins/image-search/src/test/resources/testImages/Wirestrippers.jpg new file mode 100644 index 0000000000..bddd8aacbe Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/Wirestrippers.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/adjustable_wrench_tool_3.jpg b/software/plugins/image-search/src/test/resources/testImages/adjustable_wrench_tool_3.jpg new file mode 100644 index 0000000000..aabf4b794d Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/adjustable_wrench_tool_3.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/banana2.jpg b/software/plugins/image-search/src/test/resources/testImages/banana2.jpg new file mode 100644 index 0000000000..048e905b94 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/banana2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/bin.jpg b/software/plugins/image-search/src/test/resources/testImages/bin.jpg new file mode 100644 index 0000000000..6e99c94211 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/bin.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/bin2.jpg b/software/plugins/image-search/src/test/resources/testImages/bin2.jpg new file mode 100644 index 0000000000..84b5c894d8 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/bin2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/bolt.jpg b/software/plugins/image-search/src/test/resources/testImages/bolt.jpg new file mode 100644 index 0000000000..c104639faa Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/bolt.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/box.jpg b/software/plugins/image-search/src/test/resources/testImages/box.jpg new file mode 100644 index 0000000000..9a8a9a153c Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/box.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/box2.jpg b/software/plugins/image-search/src/test/resources/testImages/box2.jpg new file mode 100644 index 0000000000..9fb96cdbf4 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/box2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/box3.jpg b/software/plugins/image-search/src/test/resources/testImages/box3.jpg new file mode 100644 index 0000000000..7479d5f9e6 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/box3.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/cayenne.jpg b/software/plugins/image-search/src/test/resources/testImages/cayenne.jpg new file mode 100644 index 0000000000..ccb1347e6a Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/cayenne.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/courgette.jpg b/software/plugins/image-search/src/test/resources/testImages/courgette.jpg new file mode 100644 index 0000000000..79464193cd Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/courgette.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/electric_jigsaw_tool_8.jpg b/software/plugins/image-search/src/test/resources/testImages/electric_jigsaw_tool_8.jpg new file mode 100644 index 0000000000..2e2fa5d247 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/electric_jigsaw_tool_8.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/fork.jpg b/software/plugins/image-search/src/test/resources/testImages/fork.jpg new file mode 100644 index 0000000000..ca0b8e49ab Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/fork.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/gloves.jpg b/software/plugins/image-search/src/test/resources/testImages/gloves.jpg new file mode 100644 index 0000000000..1a2dd7e794 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/gloves.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hammer.jpg b/software/plugins/image-search/src/test/resources/testImages/hammer.jpg new file mode 100644 index 0000000000..8035d85b80 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hammer.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hammer_tool_1.jpg b/software/plugins/image-search/src/test/resources/testImages/hammer_tool_1.jpg new file mode 100644 index 0000000000..a5b8b8e9b0 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hammer_tool_1.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hammer_tool_5.jpg b/software/plugins/image-search/src/test/resources/testImages/hammer_tool_5.jpg new file mode 100644 index 0000000000..cf509d0429 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hammer_tool_5.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/handheldrake.jpg b/software/plugins/image-search/src/test/resources/testImages/handheldrake.jpg new file mode 100644 index 0000000000..247a3d3a7d Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/handheldrake.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hoe.jpg b/software/plugins/image-search/src/test/resources/testImages/hoe.jpg new file mode 100644 index 0000000000..b79c319be4 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hoe.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hose.jpg b/software/plugins/image-search/src/test/resources/testImages/hose.jpg new file mode 100644 index 0000000000..96348ec050 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hose.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hoses.jpg b/software/plugins/image-search/src/test/resources/testImages/hoses.jpg new file mode 100644 index 0000000000..6ba377d06c Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hoses.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/hoses2.jpg b/software/plugins/image-search/src/test/resources/testImages/hoses2.jpg new file mode 100644 index 0000000000..840a88c893 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/hoses2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/knife.jpg b/software/plugins/image-search/src/test/resources/testImages/knife.jpg new file mode 100644 index 0000000000..b7f8e67622 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/knife.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/knife2.jpg b/software/plugins/image-search/src/test/resources/testImages/knife2.jpg new file mode 100644 index 0000000000..835e7e1c5d Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/knife2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/lantern.jpg b/software/plugins/image-search/src/test/resources/testImages/lantern.jpg new file mode 100644 index 0000000000..e171c44a3a Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/lantern.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/lantern2.jpg b/software/plugins/image-search/src/test/resources/testImages/lantern2.jpg new file mode 100644 index 0000000000..ae8ab07901 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/lantern2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/lantern3.jpg b/software/plugins/image-search/src/test/resources/testImages/lantern3.jpg new file mode 100644 index 0000000000..4ccb1a0aba Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/lantern3.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/ledduce.jpg b/software/plugins/image-search/src/test/resources/testImages/ledduce.jpg new file mode 100644 index 0000000000..908d1c9571 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/ledduce.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/lightbulb.jpg b/software/plugins/image-search/src/test/resources/testImages/lightbulb.jpg new file mode 100644 index 0000000000..550d37ac70 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/lightbulb.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/lights.jpg b/software/plugins/image-search/src/test/resources/testImages/lights.jpg new file mode 100644 index 0000000000..cde5a7153c Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/lights.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/manguo.jpg b/software/plugins/image-search/src/test/resources/testImages/manguo.jpg new file mode 100644 index 0000000000..3482ecbcf4 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/manguo.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/mustard.jpg b/software/plugins/image-search/src/test/resources/testImages/mustard.jpg new file mode 100644 index 0000000000..ca38994e3a Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/mustard.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/needle_nose_pliers_tool_9.jpg b/software/plugins/image-search/src/test/resources/testImages/needle_nose_pliers_tool_9.jpg new file mode 100644 index 0000000000..089c7cc351 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/needle_nose_pliers_tool_9.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/nut.jpg b/software/plugins/image-search/src/test/resources/testImages/nut.jpg new file mode 100644 index 0000000000..b5bfc5ac65 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/nut.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/open_end_wrench_tool_7.jpg b/software/plugins/image-search/src/test/resources/testImages/open_end_wrench_tool_7.jpg new file mode 100644 index 0000000000..1da71b5099 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/open_end_wrench_tool_7.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/oregano.jpg b/software/plugins/image-search/src/test/resources/testImages/oregano.jpg new file mode 100644 index 0000000000..a481c3309c Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/oregano.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/paintroller.jpg b/software/plugins/image-search/src/test/resources/testImages/paintroller.jpg new file mode 100644 index 0000000000..7a80cdab70 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/paintroller.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/paintroller2.jpg b/software/plugins/image-search/src/test/resources/testImages/paintroller2.jpg new file mode 100644 index 0000000000..221cc1f76e Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/paintroller2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/paprika.jpg b/software/plugins/image-search/src/test/resources/testImages/paprika.jpg new file mode 100644 index 0000000000..6f9b1ee4fb Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/paprika.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/pepper.jpg b/software/plugins/image-search/src/test/resources/testImages/pepper.jpg new file mode 100644 index 0000000000..769091d830 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/pepper.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/pineappple.jpg b/software/plugins/image-search/src/test/resources/testImages/pineappple.jpg new file mode 100644 index 0000000000..002d3476f2 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/pineappple.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/rake2.jpg b/software/plugins/image-search/src/test/resources/testImages/rake2.jpg new file mode 100644 index 0000000000..7ad376284c Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/rake2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/screwdriver.jpg b/software/plugins/image-search/src/test/resources/testImages/screwdriver.jpg new file mode 100644 index 0000000000..b098bfa6b4 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/screwdriver.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/screwdriver_tool_2.jpg b/software/plugins/image-search/src/test/resources/testImages/screwdriver_tool_2.jpg new file mode 100644 index 0000000000..84896a2453 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/screwdriver_tool_2.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/seasoning.jpg b/software/plugins/image-search/src/test/resources/testImages/seasoning.jpg new file mode 100644 index 0000000000..45ee76999d Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/seasoning.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/spoon.jpg b/software/plugins/image-search/src/test/resources/testImages/spoon.jpg new file mode 100644 index 0000000000..b87a0cc9da Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/spoon.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/tournevis.jpg b/software/plugins/image-search/src/test/resources/testImages/tournevis.jpg new file mode 100644 index 0000000000..48b101b366 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/tournevis.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/trowel.jpg b/software/plugins/image-search/src/test/resources/testImages/trowel.jpg new file mode 100644 index 0000000000..c034f3c1b6 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/trowel.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/utility_knife_tool_10.jpg b/software/plugins/image-search/src/test/resources/testImages/utility_knife_tool_10.jpg new file mode 100644 index 0000000000..76eae23096 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/utility_knife_tool_10.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/vherry.jpg b/software/plugins/image-search/src/test/resources/testImages/vherry.jpg new file mode 100644 index 0000000000..fa5431130a Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/vherry.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/washer.jpg b/software/plugins/image-search/src/test/resources/testImages/washer.jpg new file mode 100644 index 0000000000..d8331958bb Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/washer.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/watch.jpg b/software/plugins/image-search/src/test/resources/testImages/watch.jpg new file mode 100644 index 0000000000..a13e69e979 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/watch.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/watermelon.jpg b/software/plugins/image-search/src/test/resources/testImages/watermelon.jpg new file mode 100644 index 0000000000..a06a8f7341 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/watermelon.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/wirecutters.jpg b/software/plugins/image-search/src/test/resources/testImages/wirecutters.jpg new file mode 100644 index 0000000000..9298f0acf1 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/wirecutters.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/woodscrew.jpg b/software/plugins/image-search/src/test/resources/testImages/woodscrew.jpg new file mode 100644 index 0000000000..c8c1a85115 Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/woodscrew.jpg differ diff --git a/software/plugins/image-search/src/test/resources/testImages/wrench.jpg b/software/plugins/image-search/src/test/resources/testImages/wrench.jpg new file mode 100644 index 0000000000..7ca9a2c34a Binary files /dev/null and b/software/plugins/image-search/src/test/resources/testImages/wrench.jpg differ