From c4f50343ef5f991cc3c0184cef0fa83ff1d7f03c Mon Sep 17 00:00:00 2001 From: pujavs <43700552+pujavs@users.noreply.github.com> Date: Tue, 10 Oct 2023 20:51:19 +0530 Subject: [PATCH] feat(config-api, keycloak): saml plugin to create trust client in DB and keycloak storage provider to jans store (#6155) * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): sync with main * feat(config-api): saml plugin wip * feat(config-api): sync with main * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin - client endpoints * feat(config-api): saml plugin - client endpoints * feat(config-api): saml plugin - client endpoints * feat(config-api): saml plugin code wip * feat(config-api): saml plugin code wip * feat(config-api): sync with main * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin wip * feat(config-api): saml-plugin - wip * feat(config-api): rename saml model class * feat(config-api): saml plugin schema changes * feat(config-api): saml schema changes * feat(config-api): saml plugin schema * feat(config-api): saml plugin with new schema * feat(config-api): saml plugin data * feat(config-api): saml plugin code to save in DB * feat(config-api): rectification of model class * feat(config-api): saml line meta file wip * feat(config-api): saml plugin persist in DB code * feat(config-api): saml plugin persist in DB code * feat(config-api): sync with main * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin wip * feat(config-api): saml plugin * feat(config-api): saml plugin * feat(config-api): saml plugin * feat(config-api): saml plugin with local document store * feat(config-api): saml plugin * feat(config-api): saml plugin wip * feat(config-api): sync with main * feat(config-api): saml-plugin * feat(config-api): saml-plugin code * feat(config-api): saml-plugin code * feat(config-api): saml plugin * feat(config-api): saml plugin changes for metafile uplaod * feat(config-api): saml-plugin wip * feat(config-api): saml plugin meta-data file uplaod * feat(config-api): saml config changes wip * feat(config-api): saml config changes wip * feat(config-api): saml config wip * feat(jans-linux-setup): saml installer template * feat(config-api): saml config code wip * feat(config-api): saml config endpoint * feat(config-api): saml config endpoint * feat(config-api): spec change * feat(config-api): sync with main * feat(config-api): sync with main * feat(config-api): saml config endpoint * feat(config-api): saml conffig endpoint * feat(config-api): saml conffig endpoint * feat(config-api): saml conffig endpoint * feat(config-api): saml plugin removed keycloak ref * feat(config-api): saml plugin spec rectification * feat(config-api): saml plugin config endpoint * fix(jans-linux-setup): prompt for saml installation * feat(config-api): implemented code review comment * feat(config-api): metadata file validation * feat(config-api): saml plugin metadata file validations * feat(config-api): metafile validation * feat(config-api): metafile validation * feat(config-api): metafile validation * feat(config-api): metadata file validation dependencies * feat(config-api): metadata file validation dependencies * feat(config-api): saml metafile validation wip * feat(config-api): metafile validation * feat(config-api): sync with main * feat(config-api): metadata file pom changes * feat(config-api): metadata file pom changes * feat(config-api): metadatafile processing * feat(config-api): metadatafile process old files * feat(config-api): metadatafile process old files * feat(config-api): spec changes * feat(config-api): schema changes * feat(config-api): saml sp metedata file validation function added * feat(config-api): user storage spi wip * feat(config-api): keycloak user storage SPI poc * feat(config-api): spec changes * feat(config-api): keycloak user storage api - wip Signed-off-by: pujavs * feat(config-api): keycloak user storage spi * feat(config-api): keycloak user storage spi * feat(config-api): user storage spi * feat(config-api): user storage spi * feat(config-api): user storage spi * feat(config-api): spec changes * feat(config-api): spec changes * feat(jans-config-api): sync with main Signed-off-by: pujavs * feat(jans-keycloak): added kc code Signed-off-by: pujavs * feat(jans-keycloak): new jans-keycloak module Signed-off-by: pujavs * feat(jans-keycloak): new jans-keycloak module Signed-off-by: pujavs * feat(jans-keycloak): new jans-keycloak module Signed-off-by: pujavs * feat(jans-keycloak): new jans-keycloak module Signed-off-by: pujavs * feat(jans-keycloak): new jans-keycloak module Signed-off-by: pujavs * feat(config-api): dependency jar zip creation Signed-off-by: pujavs * feat(config-api): sync with main Signed-off-by: pujavs * feat(config-api): removed redundant code Signed-off-by: pujavs * feat(jans-keycloak): dependency changes Signed-off-by: pujavs * feat(jans-setup): merge conflict resolution Signed-off-by: pujavs * feat(jans-setup): merge conflict resolution Signed-off-by: pujavs * feat(config-api, keycloak): code quality issue fix) Signed-off-by: pujavs * feat(config-api, keycloak): code quality issue fix) Signed-off-by: pujavs * fix(jans-linux-setup): dynamic opt path * feat(jans-linux-setup): jans-keycloak-storage-api related changes (ref: #6142) * chore(jans-linux-setup): remove non-related files from this branch * fix(jans-linux-setup): create directory before writing client ldif * feat(config-api): dependency changes Signed-off-by: pujavs * feat(jans-linux-setup): KC installation * fix(jans-linux-setup): remove backup file * fix(jans-linux-setup): rename jans-authenticator as kc-jans-authn-plugin --------- Signed-off-by: pujavs Co-authored-by: Mustafa Baser Co-authored-by: Devrim --- .../plugins/docs/fido2-plugin-swagger.yaml | 8 + .../plugins/docs/saml-plugin-swagger.yaml | 461 ++++++++++++++++++ .../plugins/docs/user-mgt-plugin-swagger.yaml | 4 +- jans-config-api/plugins/pom.xml | 1 + jans-config-api/plugins/saml-plugin/pom.xml | 318 ++++++++++++ .../src/main/assembly/assembly.xml | 39 ++ .../configuration/SamlAppInitializer.java | 90 ++++ .../SamlConfigurationFactory.java | 299 ++++++++++++ .../saml/event/MetadataValidationEvent.java | 11 + .../plugin/saml/extensions/SamlExtension.java | 6 + .../saml/form/TrustRelationshipForm.java | 64 +++ .../plugin/saml/model/EntityType.java | 56 +++ .../plugin/saml/model/MetadataFilter.java | 146 ++++++ .../plugin/saml/model/MetadataSourceType.java | 77 +++ .../saml/model/ProfileConfiguration.java | 38 ++ .../plugin/saml/model/TrustRelationship.java | 392 +++++++++++++++ .../plugin/saml/model/ValidationStatus.java | 56 +++ .../plugin/saml/model/config/IdpConfig.java | 75 +++ .../model/config/SamlAppConfiguration.java | 120 +++++ .../plugin/saml/model/config/SamlConf.java | 72 +++ .../saml/model/config/SamlConfigSource.java | 81 +++ .../plugin/saml/rest/ApiApplication.java | 44 ++ .../plugin/saml/rest/SamlConfigResource.java | 113 +++++ .../saml/rest/TrustRelationshipResource.java | 190 ++++++++ .../saml/service/SamlConfigService.java | 187 +++++++ .../plugin/saml/service/SamlIdpService.java | 185 +++++++ .../plugin/saml/service/SamlService.java | 356 ++++++++++++++ .../saml/timer/MetadataValidationTimer.java | 352 +++++++++++++ .../configapi/plugin/saml/util/Constants.java | 36 ++ .../src/main/resources/META-INF/beans.xml | 8 + .../javax.enterprise.inject.spi.Extension | 1 + .../services/javax.ws.rs.ext.Providers | 3 + ...lipse.microprofile.config.spi.ConfigSource | 1 + .../src/main/resources/quartz.properties | 4 + .../src/main/resources/saml.properties | 9 + .../io/jans/configapi/KarateTestRunner.java | 18 + .../io/jans/configapi/TestJenkinsRunner.java | 44 ++ .../feature/saml/config/saml.feature | 21 + .../saml-trust-relationship.feature | 21 + .../test/resources/karate-config-jenkins.js | 59 +++ .../src/test/resources/karate-config.js | 58 +++ .../src/test/resources/karate.properties | 5 + .../test/resources/karate_jenkins.properties | 2 + .../src/test/resources/logback-test.xml | 24 + .../src/test/resources/test.properties | 8 + .../src/test/resources/testClient.feature | 13 + .../src/test/resources/token.feature | 45 ++ jans-config-api/plugins/scim-plugin/pom.xml | 1 - jans-config-api/pom.xml | 25 +- .../default/config-api-test.properties | 2 +- .../profiles/local/test.properties | 2 +- jans-config-api/server-fips/pom.xml | 1 + jans-config-api/server/pom.xml | 12 +- .../main/resources/config-api-rs-protect.json | 138 ++++++ .../example/saml/config/saml-patch.json | 18 + .../example/saml/config/saml-put.json | 19 + .../trust-relationship-post.json | 23 + .../trust-relationship-put.json | 23 + .../configapi/core/rest/BaseResource.java | 2 +- jans-core/saml/src/pom.xml | 80 +++ jans-keycloak/.gitignore | 36 ++ jans-keycloak/pom.xml | 426 ++++++++++++++++ jans-keycloak/storage-api/.gitignore | 36 ++ jans-keycloak/storage-api/pom.xml | 200 ++++++++ .../src/main/assembly/deps-zip.xml | 26 + .../idp/keycloak/config/JansConfigSource.java | 132 +++++ .../exception/JansConfigurationException.java | 27 + .../CredentialAuthenticatingService.java | 38 ++ .../service/RemoteUserStorageProvider.java | 179 +++++++ .../RemoteUserStorageProviderFactory.java | 55 +++ .../idp/keycloak/service/ScimService.java | 191 ++++++++ .../idp/keycloak/service/UserAdapter.java | 141 ++++++ .../service/UsersApiLegacyService.java | 72 +++ .../io/jans/idp/keycloak/util/Constants.java | 33 ++ .../jans/idp/keycloak/util/JansDataUtil.java | 109 +++++ .../io/jans/idp/keycloak/util/JansUtil.java | 270 ++++++++++ ...lipse.microprofile.config.spi.ConfigSource | 1 + ...eycloak.storage.UserStorageProviderFactory | 1 + .../jans-keycloak-storage-api.properties | 9 + .../storage-api/src/main/resources/log4j2.xml | 36 ++ .../jans/idp/keycloak/TestJenkinsRunner.java | 29 ++ .../java/io/jans/jans-keycloak/AppTest.java | 38 ++ jans-linux-setup/jans_setup/app_info.json | 3 +- jans-linux-setup/jans_setup/jans_setup.py | 12 + .../jans_setup/schema/jans_schema.json | 456 ++++++++++++++--- .../jans_setup/setup_app/config.py | 1 + .../setup_app/installers/config_api.py | 4 + .../jans_setup/setup_app/installers/jans.py | 2 + .../setup_app/installers/jans_saml.py | 133 +++++ .../jans_setup/setup_app/setup_options.py | 3 + .../jans_setup/setup_app/utils/arg_parser.py | 2 +- .../jans_setup/setup_app/utils/base.py | 24 +- .../setup_app/utils/collect_properties.py | 1 + .../jans_setup/setup_app/utils/ldif_utils.py | 15 +- .../setup_app/utils/properties_utils.py | 22 + .../jans-config-api/dynamic-conf.json | 6 + .../templates/jans-saml/configuration.ldif | 6 + .../templates/jans-saml/jans-saml-config.json | 22 + .../jans_setup/templates/jans.properties | 1 + 99 files changed, 7257 insertions(+), 108 deletions(-) create mode 100644 jans-config-api/plugins/docs/saml-plugin-swagger.yaml create mode 100644 jans-config-api/plugins/saml-plugin/pom.xml create mode 100644 jans-config-api/plugins/saml-plugin/src/main/assembly/assembly.xml create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlAppInitializer.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlConfigurationFactory.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/event/MetadataValidationEvent.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/extensions/SamlExtension.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/form/TrustRelationshipForm.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/EntityType.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataFilter.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataSourceType.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ProfileConfiguration.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/TrustRelationship.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ValidationStatus.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/IdpConfig.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlAppConfiguration.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConf.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConfigSource.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/ApiApplication.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/SamlConfigResource.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/TrustRelationshipResource.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlConfigService.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlIdpService.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlService.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/timer/MetadataValidationTimer.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/util/Constants.java create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/beans.xml create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/quartz.properties create mode 100644 jans-config-api/plugins/saml-plugin/src/main/resources/saml.properties create mode 100644 jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/KarateTestRunner.java create mode 100644 jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/TestJenkinsRunner.java create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/config/saml.feature create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/trust-relationship/saml-trust-relationship.feature create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/karate-config-jenkins.js create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/karate-config.js create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/karate.properties create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/karate_jenkins.properties create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/logback-test.xml create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/test.properties create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/testClient.feature create mode 100644 jans-config-api/plugins/saml-plugin/src/test/resources/token.feature create mode 100644 jans-config-api/server/src/main/resources/example/saml/config/saml-patch.json create mode 100644 jans-config-api/server/src/main/resources/example/saml/config/saml-put.json create mode 100644 jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-post.json create mode 100644 jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-put.json create mode 100644 jans-core/saml/src/pom.xml create mode 100644 jans-keycloak/.gitignore create mode 100644 jans-keycloak/pom.xml create mode 100644 jans-keycloak/storage-api/.gitignore create mode 100644 jans-keycloak/storage-api/pom.xml create mode 100644 jans-keycloak/storage-api/src/main/assembly/deps-zip.xml create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/config/JansConfigSource.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/exception/JansConfigurationException.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/CredentialAuthenticatingService.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProvider.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProviderFactory.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/ScimService.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UserAdapter.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UsersApiLegacyService.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/Constants.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansDataUtil.java create mode 100644 jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansUtil.java create mode 100644 jans-keycloak/storage-api/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource create mode 100644 jans-keycloak/storage-api/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory create mode 100644 jans-keycloak/storage-api/src/main/resources/jans-keycloak-storage-api.properties create mode 100644 jans-keycloak/storage-api/src/main/resources/log4j2.xml create mode 100644 jans-keycloak/storage-api/src/test/java/io/jans/idp/keycloak/TestJenkinsRunner.java create mode 100644 jans-keycloak/storage-api/src/test/java/io/jans/jans-keycloak/AppTest.java create mode 100644 jans-linux-setup/jans_setup/setup_app/installers/jans_saml.py create mode 100644 jans-linux-setup/jans_setup/templates/jans-saml/configuration.ldif create mode 100644 jans-linux-setup/jans_setup/templates/jans-saml/jans-saml-config.json diff --git a/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml b/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml index 6096d2a6943..608c679f6e6 100644 --- a/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml +++ b/jans-config-api/plugins/docs/fido2-plugin-swagger.yaml @@ -132,8 +132,12 @@ components: type: string superGluuEnabled: type: boolean + sessionIdPersistInCache: + type: boolean oldU2fMigrationEnabled: type: boolean + errorReasonEnabled: + type: boolean fido2Configuration: $ref: '#/components/schemas/Fido2Configuration' Fido2Configuration: @@ -169,6 +173,10 @@ components: $ref: '#/components/schemas/RequestedParty' metadataUrlsProvider: type: string + skipDownloadMdsEnabled: + type: boolean + skipValidateMdsInAttestationEnabled: + type: boolean RequestedParty: type: object properties: diff --git a/jans-config-api/plugins/docs/saml-plugin-swagger.yaml b/jans-config-api/plugins/docs/saml-plugin-swagger.yaml new file mode 100644 index 00000000000..c033b6d2a31 --- /dev/null +++ b/jans-config-api/plugins/docs/saml-plugin-swagger.yaml @@ -0,0 +1,461 @@ +openapi: 3.0.1 +info: + title: Jans Config API - SAML + contact: + name: Gluu Support + url: https://support.gluu.org + email: xxx@gluu.org + license: + name: Apache 2.0 + url: https://github.com/JanssenProject/jans/blob/main/LICENSE + version: 1.0.0 +servers: +- url: https://jans.io/ + description: The Jans server + variables: {} +tags: +- name: SAML - Configuration +- name: SAML - Trust Relationship +paths: + /saml/samlConfig: + get: + tags: + - SAML - Configuration + summary: Gets SAML configuration properties + description: Gets SAML configuration properties + operationId: get-saml-properties + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SamlAppConfiguration' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml-config.readonly + put: + tags: + - SAML - Configuration + summary: Update SAML configuration properties + description: Update SAML configuration properties + operationId: put-saml-properties + requestBody: + description: GluuAttribute object + content: + application/json: + schema: + $ref: '#/components/schemas/SamlAppConfiguration' + examples: + Request example: + description: Request example + value: | + { + "applicationName":"saml", + "samlTrustRelationshipDn":"ou=trustRelationships,o=jans", + "samlEnabled": "true", + "selectedIdp": "keycloak", + "idpRootDir": "/opt/idp/configs/", + "idpMetadataFilePattern":"%s-idp-metadata.xml", + "spMetadataFilePattern":"%s-sp-metadata.xml", + "idpConfigs":[ + { + "configId":"keycloak", + "rootDir":"/opt/idp/configs/keycloak", + "enabled": "true", + "metadataTempDir": "/opt/idp/configs/keycloak/temp_metadata", + "metadataDir":"/opt/idp/configs/keycloak/metadata", + "metadataFilePattern":"%s-idp-metadata.xml" + } + ] + } + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SamlAppConfiguration' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml-config.write + patch: + tags: + - SAML - Configuration + summary: Partially modifies SAML configuration properties. + description: Partially modifies SAML Configuration properties. + operationId: patch-saml-properties + requestBody: + description: String representing patch-document. + content: + application/json-patch+json: + schema: + type: array + items: + $ref: '#/components/schemas/JsonPatch' + examples: + Request json example: + description: Request json example + value: | + [{ + "op": "replace", + "path": "/samlEnabled", + "value": false + }, + { + "op": "add", + "path": "/idpConfigs/1", + "value": { + "configId": "shibboleth", + "rootDir": "/opt/idp/configs/shibboleth", + "enabled": false, + "metadataTempDir": "/opt/idp/configs/shibboleth/temp_metadata", + "metadataDir": "/opt/idp/configs/shibboleth/metadata", + "metadataFilePattern": "%s-idp-metadata.xml" + } + } + ] + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SamlAppConfiguration' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml-config.write + /saml/trust-relationship/upload: + post: + tags: + - SAML - Trust Relationship + summary: Create Trust Relationship with Metadata File + description: Create Trust Relationship with Metadata File + operationId: post-trust-relationship-metadata-file + requestBody: + description: Trust Relationship object + content: + application/json: + schema: + $ref: '#/components/schemas/TrustRelationship' + examples: + Request example: + description: Request example + value: "" + responses: + "201": + description: Newly created Trust Relationship + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/TrustRelationshipForm' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.write + /saml/trust-relationship/{id}: + delete: + tags: + - SAML - Trust Relationship + summary: Delete TrustRelationship + description: Delete TrustRelationship + operationId: put-trust-relationship + parameters: + - name: id + in: path + description: Unique Id of Trust Relationship + required: true + schema: + type: string + responses: + "204": + description: No Content + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.write + /saml/trust-relationship: + get: + tags: + - SAML - Trust Relationship + summary: Get all Trust Relationship + description: Get all TrustRelationship. + operationId: get-trust-relationship + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrustRelationship' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.readonly + put: + tags: + - SAML - Trust Relationship + summary: Update TrustRelationship + description: Update TrustRelationship + operationId: put-trust-relationship_1 + requestBody: + description: Trust Relationship object + content: + application/json: + schema: + $ref: '#/components/schemas/TrustRelationship' + examples: + Request example: + description: Request example + value: "" + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TrustRelationship' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.write + /saml/trust-relationship/id/{id}: + get: + tags: + - SAML - Trust Relationship + summary: Get TrustRelationship by Id + description: Get TrustRelationship by Id + operationId: get-trust-relationship-by-id + parameters: + - name: id + in: path + description: Unique identifier - Id + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/TrustRelationship' + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.readonly + /saml/trust-relationship/PROCESS_META_FILE: + post: + tags: + - SAML - Trust Relationship + summary: Process unprocessed metadata files + description: Process unprocessed metadata files + operationId: post-metadata-files + responses: + "200": + description: OK + "401": + description: Unauthorized + "500": + description: InternalServerError + security: + - oauth2: + - https://jans.io/oauth/config/saml.write +components: + schemas: + IdpConfig: + type: object + properties: + configId: + type: string + rootDir: + type: string + enabled: + type: boolean + metadataTempDir: + type: string + metadataDir: + type: string + metadataFilePattern: + type: string + SamlAppConfiguration: + type: object + properties: + applicationName: + type: string + samlTrustRelationshipDn: + type: string + samlEnabled: + type: boolean + selectedIdp: + type: string + idpRootDir: + type: string + idpMetadataFilePattern: + type: string + spMetadataFilePattern: + type: string + spMetadataFile: + type: string + configGeneration: + type: boolean + ignoreValidation: + type: boolean + idpConfigs: + type: array + items: + $ref: '#/components/schemas/IdpConfig' + JsonPatch: + type: object + ProfileConfiguration: + type: object + properties: + name: + type: string + signResponses: + type: string + TrustRelationship: + required: + - description + - displayName + - spMetaDataSourceType + type: object + properties: + dn: + type: string + inum: + type: string + owner: + type: string + clientId: + type: string + displayName: + maxLength: 60 + minLength: 0 + type: string + description: + maxLength: 4000 + minLength: 0 + type: string + rootUrl: + type: string + adminUrl: + type: string + baseUrl: + type: string + surrogateAuthRequired: + type: boolean + enabled: + type: boolean + alwaysDisplayInConsole: + type: boolean + clientAuthenticatorType: + type: string + secret: + type: string + registrationAccessToken: + type: string + consentRequired: + type: boolean + spMetaDataSourceType: + type: string + enum: + - file + - uri + - federation + - manual + - mdq + spMetaDataURL: + type: string + metaLocation: + type: string + jansEntityId: + type: array + items: + type: string + releasedAttributes: + type: array + items: + type: string + url: + pattern: "^(https?|http)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]" + type: string + spLogoutURL: + pattern: "^$|(^(https?|http)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])" + type: string + status: + type: string + enum: + - active + - inactive + - expired + - register + validationStatus: + type: string + enum: + - In Progress + - Success + - Scheduled + - Failed + validationLog: + type: array + items: + type: string + profileConfigurations: + type: object + additionalProperties: + $ref: '#/components/schemas/ProfileConfiguration' + baseDn: + type: string + TrustRelationshipForm: + required: + - metaDataFile + - trustRelationship + type: object + properties: + trustRelationship: + $ref: '#/components/schemas/TrustRelationship' + metaDataFile: + type: object + securitySchemes: + oauth2: + type: oauth2 + flows: + clientCredentials: + tokenUrl: "https://{op-hostname}/.../token" + scopes: + https://jans.io/oauth/config/saml.readonly: View SAML related information + https://jans.io/oauth/config/saml.write: Manage SAML related information + https://jans.io/oauth/config/saml-config.readonly: View SAML configuration + related information + https://jans.io/oauth/config/saml-config.write: Manage SAML configuration + related information diff --git a/jans-config-api/plugins/docs/user-mgt-plugin-swagger.yaml b/jans-config-api/plugins/docs/user-mgt-plugin-swagger.yaml index 15718369383..a55d9387a7c 100644 --- a/jans-config-api/plugins/docs/user-mgt-plugin-swagger.yaml +++ b/jans-config-api/plugins/docs/user-mgt-plugin-swagger.yaml @@ -838,10 +838,10 @@ components: type: array items: type: object - value: - type: object displayValue: type: string + value: + type: object CustomUser: type: object properties: diff --git a/jans-config-api/plugins/pom.xml b/jans-config-api/plugins/pom.xml index e09728f7d48..27c01ebf12c 100644 --- a/jans-config-api/plugins/pom.xml +++ b/jans-config-api/plugins/pom.xml @@ -21,6 +21,7 @@ scim-plugin user-mgt-plugin fido2-plugin + saml-plugin diff --git a/jans-config-api/plugins/saml-plugin/pom.xml b/jans-config-api/plugins/saml-plugin/pom.xml new file mode 100644 index 00000000000..a8391baf28b --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/pom.xml @@ -0,0 +1,318 @@ + + + + plugins + io.jans.jans-config-api.plugins + 1.0.19-SNAPSHOT + + + 4.0.0 + saml-plugin + + + 4.4.14 + 4.5.13 + ${project.version} + 3.4.6 + + + + + + + io.jans + jans-config-api-shared + ${jans.version} + + + io.jans + jans-config-api-server + ${jans.version} + + + io.jans + jans-orm-annotation + ${jans.version} + + + io.jans + jans-core-document-store + ${jans.version} + + + io.jans + jans-core-saml + ${jans.version} + + + + org.quartz-scheduler + quartz + + + + io.smallrye + smallrye-config + 1.5.0 + + + + + commons-collections + commons-collections + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpcore-nio + ${httpcore.version} + + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + jakarta.inject + jakarta.inject-api + + + jakarta.validation + jakarta.validation-api + + + jakarta.ws.rs + jakarta.ws.rs-api + + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version} + + + + + org.apache.james + apache-mime4j-dom + + + org.apache.james + apache-mime4j-storage + + + org.apache.james + apache-mime4j-core + + + + + org.opensaml + opensaml-saml-api + ${saml.version} + + + org.opensaml + opensaml-xmlsec-api + ${saml.version} + + + org.opensaml + opensaml-core + 4.0.1 + + + org.opensaml + opensaml-security-api + ${saml.version} + + + net.shibboleth.utilities + java-support + 7.5.2 + + + + + io.rest-assured + rest-assured + test + + + com.intuit.karate + karate-junit5 + test + + + com.intuit.karate + karate-apache + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + net.masterthought + cucumber-reporting + test + + + + + io.swagger.core.v3 + swagger-core-jakarta + + + + + + + + + ../../profiles/${cfg}/config-build.properties + ../../profiles/${cfg}/config-api-test.properties + + + + + src/test/resources + true + + karate.properties + karate_jenkins.properties + test.properties + *.* + + + + + + + src/main/resources + true + + **/*.xml + **/*.properties + **/*.json + META-INF/services/*.* + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + src/main/assembly/assembly.xml + + + + + + + + maven-surefire-plugin + + + + integration + + --tags ~@ignore + + + + + integration-tests + integration-test + + test + + + false + !integration + integration + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + deploy-to-local-folder + package + + copy-resources + + + ../target/plugins + + + ${project.build.directory} + *-distribution.jar + false + + + + + + + + + + io.swagger.core.v3 + swagger-maven-plugin-jakarta + ${swagger-maven-plugin-jakarta} + + + + true + saml-plugin-swagger + ${project.artifactId} + true + + io.jans.configapi.plugin.saml.rest + + + + + + + io.swagger.core.v3 + swagger-models-jakarta + ${swagger-models-jakarta} + + + + + + + + \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/assembly/assembly.xml b/jans-config-api/plugins/saml-plugin/src/main/assembly/assembly.xml new file mode 100644 index 00000000000..8e93631d7cd --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/assembly/assembly.xml @@ -0,0 +1,39 @@ + + + distribution + + jar + + false + + + true + / + false + + io.jans:jans-core-saml + org.jboss.resteasy:resteasy-multipart-provider + org.apache.james:apache-mime4j-dom + org.apache.james:apache-mime4j-storage + org.apache.james:apache-mime4j-core + org.opensaml:opensaml-saml-api + org.opensaml:xmltooling + org.opensaml:opensaml-xmlsec-api + org.opensaml:opensaml-core + net.shibboleth.utilities:java-support + + runtime + + + + + ${project.build.directory}/classes + / + + **/* + + + + \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlAppInitializer.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlAppInitializer.java new file mode 100644 index 00000000000..ce2ef82ef05 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlAppInitializer.java @@ -0,0 +1,90 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.configuration; + +import io.jans.as.common.service.common.ApplicationFactory; +import io.jans.configapi.plugin.saml.timer.MetadataValidationTimer; +import io.jans.orm.PersistenceEntryManager; +import io.jans.service.timer.QuartzSchedulerManager; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.BeforeDestroyed; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.servlet.ServletContext; + +import org.slf4j.Logger; + +@ApplicationScoped +@Named("samlAppInitializer") +public class SamlAppInitializer { + + @Inject + Logger log; + + @Inject + @Named(ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME) + Instance persistenceEntryManagerInstance; + + @Inject + BeanManager beanManager; + + @Inject + SamlConfigurationFactory samlConfigurationFactory; + + + @Inject + QuartzSchedulerManager quartzSchedulerManager; + + @Inject + MetadataValidationTimer metadataValidationTimer; + + + public void onAppStart(@Observes @Initialized(ApplicationScoped.class) Object init) { + log.info("============= Initializing SAML Plugin ========================"); + log.debug("init:{}", init); + + // configuration + this.samlConfigurationFactory.create(); + initSchedulerService(); + metadataValidationTimer.initTimer(); + + log.info("============== SAML Plugin IS UP AND RUNNING ==================="); + } + + + protected void initSchedulerService() { + log.debug("Initializing Scheduler Service"); + quartzSchedulerManager.start(); + + String disableScheduler = System.getProperties().getProperty("gluu.disable.scheduler"); + if (Boolean.parseBoolean(disableScheduler)) { + this.log.warn("Suspending Quartz Scheduler Service..."); + quartzSchedulerManager.standby(); + } + } + + public void destroy(@Observes @BeforeDestroyed(ApplicationScoped.class) ServletContext init) { + log.info("================================================================"); + log.info("=========== SAML Plugin STOPPED =========================="); + log.info("init:{}", init); + log.info("================================================================"); + } + + @Produces + @ApplicationScoped + public SamlConfigurationFactory getSamlConfigurationFactory() { + return samlConfigurationFactory; + } + + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlConfigurationFactory.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlConfigurationFactory.java new file mode 100644 index 00000000000..3dfafb103b9 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/configuration/SamlConfigurationFactory.java @@ -0,0 +1,299 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.configuration; + +import io.jans.as.common.service.common.ApplicationFactory; +import io.jans.as.model.config.Constants; +import io.jans.configapi.plugin.saml.model.config.SamlConf; +import io.jans.configapi.plugin.saml.model.config.SamlAppConfiguration; +import io.jans.as.model.configuration.Configuration; +import io.jans.exception.ConfigurationException; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.exception.BasePersistenceException; +import io.jans.orm.service.PersistanceFactoryService; +import io.jans.orm.util.properties.FileConfiguration; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.BaseConfigurationReload; +import io.jans.service.cdi.event.ConfigurationEvent; +import io.jans.service.cdi.event.ConfigurationUpdate; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; +import org.slf4j.Logger; + +import java.io.File; +import java.util.concurrent.atomic.AtomicBoolean; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +@ApplicationScoped +@Alternative +@Priority(1) +public class SamlConfigurationFactory { + + public static final String SAML_CONFIGURATION_ENTRY_DN = "saml_ConfigurationEntryDN"; + + static { + if (System.getProperty("jans.base") != null) { + BASE_DIR = System.getProperty("jans.base"); + } else if ((System.getProperty("catalina.base") != null) + && (System.getProperty("catalina.base.ignore") == null)) { + BASE_DIR = System.getProperty("catalina.base"); + } else if (System.getProperty("catalina.home") != null) { + BASE_DIR = System.getProperty("catalina.home"); + } else if (System.getProperty("jboss.home.dir") != null) { + BASE_DIR = System.getProperty("jboss.home.dir"); + } else { + BASE_DIR = null; + } + } + + @Inject + private Logger log; + + @Inject + private Event timerEvent; + + @Inject + private Event samlConfigurationUpdateEvent; + + @Inject + private Event event; + + @Inject + @Named(ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME) + private Instance persistenceEntryManagerInstance; + + @Inject + private PersistanceFactoryService persistanceFactoryService; + + @Inject + private Instance configurationInstance; + + // timer events + public static final String PERSISTENCE_CONFIGUARION_RELOAD_EVENT_TYPE = "persistenceConfigurationReloadEvent"; + public static final String SAML_BASE_CONFIGURATION_RELOAD_EVENT_TYPE = "saml_baseConfigurationReloadEvent"; + + private static final int DEFAULT_INTERVAL = 30; // 30 seconds + private AtomicBoolean isActive; + private long baseConfigurationFileLastModifiedTime; + + // base dir + private static final String BASE_DIR; + private static final String DIR = BASE_DIR + File.separator + "conf" + File.separator; + private static final String BASE_PROPERTIES_FILE = DIR + Constants.BASE_PROPERTIES_FILE_NAME; + + // saml config + public static final String SAML_CONFIGURATION_ENTRY = "saml_ConfigurationEntryDN"; + private SamlAppConfiguration samlAppConfiguration; + private boolean samlConfigLoaded = false; + private long samlLoadedRevision = -1; + private FileConfiguration baseConfiguration; + + public String getSamlConfigurationDn() { + return this.baseConfiguration.getString(SAML_CONFIGURATION_ENTRY_DN); + } + + public FileConfiguration getBaseConfiguration() { + return baseConfiguration; + } + + @PostConstruct + public void init() { + log.info("Initializing SamlConfigurationFactory "); + this.isActive = new AtomicBoolean(true); + try { + + loadBaseConfiguration(); + + } finally { + this.isActive.set(false); + } + } + + @Produces + @ApplicationScoped + public SamlAppConfiguration getSamlAppConfiguration() { + return samlAppConfiguration; + } + + + public void create() { + log.info("Loading SAML Configuration"); + + // load SAML config from DB + if (!loadSamlConfigFromDb()) { + log.error("Failed to load SAML configuration from persistence. Please fix it!!!."); + throw new ConfigurationException("Failed to load SAML configuration from persistence."); + } else { + log.error("SAML Configuration loaded successfully - samlLoadedRevision:{}, samlAppConfiguration:{}", + this.samlLoadedRevision, getSamlAppConfiguration()); + } + + + } + + public String getSamlAppConfigurationDn() { + return this.baseConfiguration.getString(SAML_CONFIGURATION_ENTRY); + } + + public String getConfigurationDn(String key) { + return this.baseConfiguration.getString(key); + } + + private void loadBaseConfiguration() { + log.debug("Loading base configuration - BASE_PROPERTIES_FILE:{}", BASE_PROPERTIES_FILE); + + this.baseConfiguration = createFileConfiguration(BASE_PROPERTIES_FILE); + this.baseConfigurationFileLastModifiedTime = new File(BASE_PROPERTIES_FILE).lastModified(); + + log.debug("Loaded base configuration:{}", baseConfiguration.getProperties()); + } + + private FileConfiguration createFileConfiguration(String fileName) { + try { + return new FileConfiguration(fileName); + } catch (Exception ex) { + if (log.isErrorEnabled()) { + log.error("Failed to load configuration from {}", fileName, ex); + } + throw new ConfigurationException("Failed to load configuration from " + fileName, ex); + } + } + + private boolean loadSamlConfigFromDb() { + log.debug("Loading Api configuration from '{}' DB...", baseConfiguration.getString("persistence.type")); + try { + final SamlConf samlConf = loadConfigurationFromDb(getConfigurationDn(SAML_CONFIGURATION_ENTRY), + new SamlConf()); + log.trace("Conf configuration '{}' DB...", samlConf); + + if (samlConf != null) { + initSamlConf(samlConf); + + // Destroy old configuration + if (this.samlConfigLoaded) { + destroy(SamlAppConfiguration.class); + } + + this.samlConfigLoaded = true; + samlConfigurationUpdateEvent.select(ConfigurationUpdate.Literal.INSTANCE).fire(samlAppConfiguration); + + return true; + } + } catch (Exception ex) { + log.error("Unable to find api configuration in DB..." + ex.getMessage(), ex); + } + return false; + } + + private void initSamlConf(SamlConf samlConf) { + log.debug("Initializing SAML Configuration From DB.... samlConf:{}", samlConf); + + if (samlConf == null) { + throw new ConfigurationException("Failed to load SAML Configuration From DB " + samlConf); + } + + log.info("samlAppConfigurationFromDb:{}",samlConf); + if (samlConf.getDynamicConf() != null) { + this.samlAppConfiguration = samlConf.getDynamicConf(); + } + + this.samlLoadedRevision = samlConf.getRevision(); + + log.debug("*** samlAppConfiguration:{}, samlLoadedRevision:{} ", + this.samlAppConfiguration, samlLoadedRevision); + + } + + + private T loadConfigurationFromDb(String dn, T obj, String... returnAttributes) { + log.debug("loadConfigurationFromDb dn:{}, clazz:{}, returnAttributes:{}", dn, obj, returnAttributes); + final PersistenceEntryManager persistenceEntryManager = persistenceEntryManagerInstance.get(); + try { + return (T) persistenceEntryManager.find(dn, obj.getClass(), returnAttributes); + } catch (BasePersistenceException ex) { + log.error(ex.getMessage()); + return null; + } + } + + private boolean isSamlRevisionIncreased() { + final SamlConf samlConf = loadConfigurationFromDb(getConfigurationDn(SAML_CONFIGURATION_ENTRY_DN), + new SamlConf(), "jansRevision"); + if (samlConf == null) { + return false; + } + + log.debug("Saml Config - DB revision: {}, server revision: {}", samlConf.getRevision(), samlLoadedRevision); + return samlConf.getRevision() > this.samlLoadedRevision; + } + + public boolean reloadSamlConfFromLdap() { + log.debug("Reload api configuration TimerEvent"); + if (!isSamlRevisionIncreased()) { + return false; + } + return this.loadSamlConfigFromDb(); + } + + public void destroy(Class clazz) { + Instance confInstance = configurationInstance.select(clazz); + configurationInstance.destroy(confInstance.get()); + } + + public void initTimer() { + log.debug("Initializing Configuration Timer"); + + final int delay = 30; + + timerEvent.fire(new TimerEvent(new TimerSchedule(delay, DEFAULT_INTERVAL), new ConfigurationEvent(), + Scheduled.Literal.INSTANCE)); + } + + @Asynchronous + public void reloadConfigurationTimerEvent(@Observes @Scheduled ConfigurationEvent configurationEvent) { + log.debug("Config reload configuration TimerEvent - baseConfigurationFileLastModifiedTime:{}", + baseConfigurationFileLastModifiedTime); + + // Reload Base configuration if needed + File baseConf = new File(BASE_PROPERTIES_FILE); + if (baseConf.exists()) { + final long lastModified = baseConf.lastModified(); + if (lastModified > baseConfigurationFileLastModifiedTime) { + // Reload configuration only if it was modified + loadBaseConfiguration(); + event.select(BaseConfigurationReload.Literal.INSTANCE).fire(SAML_BASE_CONFIGURATION_RELOAD_EVENT_TYPE); + } + } + + if (this.isActive.get()) { + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + return; + } + + try { + reloadSamlConfFromLdap(); + } catch (Exception ex) { + log.error("Exception happened while reloading application configuration", ex); + } finally { + this.isActive.set(false); + } + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/event/MetadataValidationEvent.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/event/MetadataValidationEvent.java new file mode 100644 index 00000000000..67bc129f442 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/event/MetadataValidationEvent.java @@ -0,0 +1,11 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ +package io.jans.configapi.plugin.saml.event; + + +public class MetadataValidationEvent { + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/extensions/SamlExtension.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/extensions/SamlExtension.java new file mode 100644 index 00000000000..7ea4ca66ea5 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/extensions/SamlExtension.java @@ -0,0 +1,6 @@ +package io.jans.configapi.plugin.saml.extensions; + +import jakarta.enterprise.inject.spi.Extension; + +public class SamlExtension implements Extension { +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/form/TrustRelationshipForm.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/form/TrustRelationshipForm.java new file mode 100644 index 00000000000..c97eabaa7d0 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/form/TrustRelationshipForm.java @@ -0,0 +1,64 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.form; + +import io.jans.configapi.plugin.saml.model.TrustRelationship; + +import java.io.Serializable; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.Valid; + + +import java.io.InputStream; + +import org.jboss.resteasy.annotations.providers.multipart.PartType; +import jakarta.ws.rs.FormParam; +import jakarta.ws.rs.core.MediaType; + +public class TrustRelationshipForm implements Serializable { + + private static final long serialVersionUID = 1L; + + @NotNull + @Valid + @FormParam("trustRelationship") + @PartType(MediaType.APPLICATION_JSON) + private TrustRelationship trustRelationship; + + @NotNull + @FormParam("metaDataFile") + @PartType(MediaType.APPLICATION_OCTET_STREAM) + private transient InputStream metaDataFile; + + public TrustRelationship getTrustRelationship() { + return trustRelationship; + } + + public void setTrustRelationship(TrustRelationship trustRelationship) { + this.trustRelationship = trustRelationship; + } + + public InputStream getMetaDataFile() { + return metaDataFile; + } + + public void setMetaDataFile(InputStream metaDataFile) { + this.metaDataFile = metaDataFile; + } + + @Override + public String toString() { + return "TrustRelationshipForm [" + + "trustRelationship=" + trustRelationship + + "metaDataFile=" + metaDataFile + + "]"; + } + + + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/EntityType.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/EntityType.java new file mode 100644 index 00000000000..1e218fa715d --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/EntityType.java @@ -0,0 +1,56 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + +import java.util.HashMap; +import java.util.Map; +import io.jans.orm.annotation.AttributeEnum; + + +public enum EntityType implements AttributeEnum { + + SingleSP("Single SP", "Single SP"), FederationAggregate("Federation/Aggregate", "Federation/Aggregate"); + + private final String value; + private final String displayName; + + private static final Map mapByValues = new HashMap(); + static { + for (EntityType enumType : values()) { + mapByValues.put(enumType.getValue(), enumType); + } + } + + private EntityType(String value, String displayName) { + this.value = value; + this.displayName = displayName; + } + + @Override + public String getValue() { + return value; + } + + public String getDisplayName() { + return displayName; + } + + public static EntityType getByValue(String value) { + return mapByValues.get(value); + } + + @Override + public Enum resolveByValue(String value) { + return getByValue(value); + } + + @Override + public String toString() { + return value; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataFilter.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataFilter.java new file mode 100644 index 00000000000..288ae9a4d18 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataFilter.java @@ -0,0 +1,146 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + +import java.io.Serializable; +import java.util.List; + +public class MetadataFilter implements Serializable { + + + private String name; + private String description; + private List extensionSchemas; + private String extensionSchema; + private boolean removeRolelessEntityDescriptors; + private boolean removeEmptyEntitiesDescriptors; + private String retainedRole; + private List retainedRoles; + private int maxValidityInterval; + private String id; + private String certPath; + private boolean requireSignedMetadata; + private String filterCertFileName; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof MetadataFilter) && name != null && name.equals(((MetadataFilter) obj).getName()); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + public void setExtensionSchemas(List extensionSchemas) { + this.extensionSchemas = extensionSchemas; + } + + public List getExtensionSchemas() { + return this.extensionSchemas; + } + + public void setExtensionSchema(String extensionSchema) { + this.extensionSchema = extensionSchema; + } + + public String getExtensionSchema() { + return extensionSchema; + } + + public boolean getRemoveRolelessEntityDescriptors() { + return removeRolelessEntityDescriptors; + } + + public void setRemoveRolelessEntityDescriptors(boolean removeRolelessEntityDescriptors) { + this.removeRolelessEntityDescriptors = removeRolelessEntityDescriptors; + } + + public boolean getRemoveEmptyEntitiesDescriptors() { + return removeEmptyEntitiesDescriptors; + } + + public void setRemoveEmptyEntitiesDescriptors(boolean removeEmptyEntitiesDescriptors) { + this.removeEmptyEntitiesDescriptors = removeEmptyEntitiesDescriptors; + + } + + public String getRetainedRole() { + return retainedRole; + } + + public void setRetainedRole(String retainedRole) { + this.retainedRole = retainedRole; + } + + public List getRetainedRoles() { + return retainedRoles; + } + + public void setRetainedRoles(List retainedRoles) { + this.retainedRoles = retainedRoles; + + } + + public void setMaxValidityInterval(int maxValidityInterval) { + this.maxValidityInterval = maxValidityInterval; + } + + public int getMaxValidityInterval() { + return maxValidityInterval; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return this.id; + } + + public void setCertPath(String certPath) { + this.certPath = certPath; + } + + public String getCertPath() { + return this.certPath; + } + + public void setRequireSignedMetadata(boolean requireSignedMetadata) { + this.requireSignedMetadata = requireSignedMetadata; + } + + public boolean getRequireSignedMetadata() { + return this.requireSignedMetadata; + } + + public void setFilterCertFileName(String filterCertFileName) { + this.filterCertFileName = filterCertFileName; + + } + + public String getFilterCertFileName() { + return this.filterCertFileName; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataSourceType.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataSourceType.java new file mode 100644 index 00000000000..c8a5597dfcf --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/MetadataSourceType.java @@ -0,0 +1,77 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + +import java.util.HashMap; +import java.util.Map; +import io.jans.orm.annotation.AttributeEnum; + +/** + * Metadata source type + * + */ +public enum MetadataSourceType implements AttributeEnum { + + FILE("file", "File",1), URI("uri", "URI",2), FEDERATION("federation", "Federation",3), MANUAL("manual", "Manual",4), MDQ("mdq", "MDQ",5); + + private final String value; + private final String displayName; + private final int rank; // used for ordering + + private static final Map mapByValues = new HashMap(); + static { + for (MetadataSourceType enumType : values()) { + mapByValues.put(enumType.getValue(), enumType); + } + } + + private MetadataSourceType(String value, String displayName,int rank) { + this.value = value; + this.displayName = displayName; + this.rank = rank; + } + + @Override + public String getValue() { + return value; + } + + public String getDisplayName() { + return displayName; + } + + public int getRank() { + + return this.rank; + } + + public static MetadataSourceType getByValue(String value) { + return mapByValues.get(value); + } + + @Override + public Enum resolveByValue(String value) { + return getByValue(value); + } + + @Override + public String toString() { + return value; + } + + public static boolean contains(String name) { + boolean result = false; + for (MetadataSourceType direction : values()) { + if (direction.name().equalsIgnoreCase(name)) { + result = true; + break; + } + } + return result; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ProfileConfiguration.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ProfileConfiguration.java new file mode 100644 index 00000000000..91c3e151b21 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ProfileConfiguration.java @@ -0,0 +1,38 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ProfileConfiguration implements Serializable { + + private String name; + private String signResponses; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String getSignResponses() { + return signResponses; + } + + public void setSignResponses(String signResponses) { + this.signResponses = signResponses; + } + + + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/TrustRelationship.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/TrustRelationship.java new file mode 100644 index 00000000000..7518f1057e6 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/TrustRelationship.java @@ -0,0 +1,392 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + + +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.jans.model.GluuStatus; +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.ObjectClass; +import io.jans.orm.model.base.Entry; +import io.swagger.v3.oas.annotations.Hidden; + +import java.util.Collections; +import java.util.List; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.io.Serializable; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + + +@DataEntry(sortBy = { "displayName" }) +@ObjectClass(value = "jansSAMLconfig") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TrustRelationship extends Entry implements Serializable { + + private static final long serialVersionUID = 7912166229997681502L; + + @AttributeName(ignoreDuringUpdate = true) + private String inum; + + @AttributeName + private String owner; + + @AttributeName(name = "jansClntId") + private String clientId; + + @NotNull + @Size(min = 0, max = 60, message = "Length of the Display Name should not exceed 60") + @AttributeName + private String displayName; + + @NotNull + @Size(min = 0, max = 4000, message = "Length of the Description should not exceed 4000") + @AttributeName + private String description; + + // Access settings + /** + * Root URL appended to relative URLs + */ + @AttributeName + private String rootUrl; + + /** + * URL to the admin interface of the client. + * + */ + @AttributeName + private String adminUrl; + + /** + * Default URL, Home URL to use when the auth server needs to redirect or link + * back to the client. + * + */ + @AttributeName + private String baseUrl; + + @AttributeName(name = "surrogateAuthRequired") + private boolean surrogateAuthRequired; + + @AttributeName(name = "jansEnabled") + private boolean enabled; + + /** + * Always list this client in the Account UI, even if the user does not have an + * active session. + */ + @AttributeName(name = "displayInConsole") + private boolean alwaysDisplayInConsole; + + @AttributeName(name = "jansPreferredMethod") + private String clientAuthenticatorType; + + @AttributeName(name = "jansClntSecret") + private String secret; + + @AttributeName(name = "jansRegistrationAccessTkn") + private String registrationAccessToken; + + private Boolean consentRequired; + + /** + * Trust Relationship SP metadata type - file, URI, federation + */ + @NotNull + @AttributeName(name = "jansSAMLspMetaDataSourceTyp") + private MetadataSourceType spMetaDataSourceType; + + /** + * Trust Relationship file location of metadata + */ + @AttributeName(name = "jansSAMLspMetaDataFN") + @Hidden + private String spMetaDataFN; + + @AttributeName(name = "jansSAMLspMetaDataURL") + private String spMetaDataURL; + + @AttributeName(name = "jansMetaLocation") + private String metaLocation; + + @AttributeName(name = "jansEntityId") + private List jansEntityId; + + @AttributeName(name = "jansReleasedAttr") + private List releasedAttributes; + + @Pattern(regexp = "^(https?|http)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]", message = "Please enter a valid SP url, including protocol (http/https)") + @AttributeName(name = "url") + private String url; + + @Pattern(regexp = "^$|(^(https?|http)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])", message = "Please enter a valid url, including protocol (http/https)") + @AttributeName(name = "jansPostLogoutRedirectURI") + private String spLogoutURL; + + @AttributeName(name = "jansStatus") + private GluuStatus status; + + @AttributeName(name = "jansValidationStatus") + private ValidationStatus validationStatus; + + @AttributeName(name = "jansValidationLog") + private List validationLog; + + private Map profileConfigurations = new HashMap(); + + public String getInum() { + return inum; + } + + public void setInum(String inum) { + this.inum = inum; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getRootUrl() { + return rootUrl; + } + + public void setRootUrl(String rootUrl) { + this.rootUrl = rootUrl; + } + + public String getAdminUrl() { + return adminUrl; + } + + public void setAdminUrl(String adminUrl) { + this.adminUrl = adminUrl; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public boolean isSurrogateAuthRequired() { + return surrogateAuthRequired; + } + + public void setSurrogateAuthRequired(boolean surrogateAuthRequired) { + this.surrogateAuthRequired = surrogateAuthRequired; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isAlwaysDisplayInConsole() { + return alwaysDisplayInConsole; + } + + public void setAlwaysDisplayInConsole(boolean alwaysDisplayInConsole) { + this.alwaysDisplayInConsole = alwaysDisplayInConsole; + } + + public String getClientAuthenticatorType() { + return clientAuthenticatorType; + } + + public void setClientAuthenticatorType(String clientAuthenticatorType) { + this.clientAuthenticatorType = clientAuthenticatorType; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + + public Boolean getConsentRequired() { + return consentRequired; + } + + public void setConsentRequired(Boolean consentRequired) { + this.consentRequired = consentRequired; + } + + public MetadataSourceType getSpMetaDataSourceType() { + return spMetaDataSourceType; + } + + public void setSpMetaDataSourceType(MetadataSourceType spMetaDataSourceType) { + this.spMetaDataSourceType = spMetaDataSourceType; + } + + public String getSpMetaDataFN() { + return spMetaDataFN; + } + + public void setSpMetaDataFN(String spMetaDataFN) { + this.spMetaDataFN = spMetaDataFN; + } + + public String getSpMetaDataURL() { + return spMetaDataURL; + } + + public void setSpMetaDataURL(String spMetaDataURL) { + this.spMetaDataURL = spMetaDataURL; + } + + public String getMetaLocation() { + return metaLocation; + } + + public void setMetaLocation(String metaLocation) { + this.metaLocation = metaLocation; + } + + public List getJansEntityId() { + return jansEntityId; + } + + public void setJansEntityId(List jansEntityId) { + this.jansEntityId = jansEntityId; + } + + public List getReleasedAttributes() { + return releasedAttributes; + } + + public void setReleasedAttributes(List releasedAttributes) { + this.releasedAttributes = releasedAttributes; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getSpLogoutURL() { + return spLogoutURL; + } + + public void setSpLogoutURL(String spLogoutURL) { + this.spLogoutURL = spLogoutURL; + } + + public GluuStatus getStatus() { + return status; + } + + public void setStatus(GluuStatus status) { + this.status = status; + } + + public ValidationStatus getValidationStatus() { + return validationStatus; + } + + public void setValidationStatus(ValidationStatus validationStatus) { + this.validationStatus = validationStatus; + } + + public List getValidationLog() { + return validationLog; + } + + public void setValidationLog(List validationLog) { + this.validationLog = validationLog; + } + + public Map getProfileConfigurations() { + return profileConfigurations; + } + + public void setProfileConfigurations(Map profileConfigurations) { + this.profileConfigurations = profileConfigurations; + } + + private static class SortByDatasourceTypeComparator implements Comparator { + + public int compare(TrustRelationship first, TrustRelationship second) { + + return first.getSpMetaDataSourceType().getRank() - second.getSpMetaDataSourceType().getRank(); + } + } + + public static void sortByDataSourceType(List trustRelationships) { + Collections.sort(trustRelationships, new SortByDatasourceTypeComparator()); + } + + + @Override + public String toString() { + return "TrustRelationship [inum=" + inum + ", owner=" + owner + ", clientId=" + clientId + ", displayName=" + + displayName + ", description=" + description + ", rootUrl=" + rootUrl + ", adminUrl=" + adminUrl + + ", baseUrl=" + baseUrl + ", surrogateAuthRequired=" + surrogateAuthRequired + ", enabled=" + enabled + + ", alwaysDisplayInConsole=" + alwaysDisplayInConsole + ", clientAuthenticatorType=" + + clientAuthenticatorType + ", secret=" + secret + ", registrationAccessToken=" + + registrationAccessToken + ", consentRequired=" + consentRequired + ", spMetaDataSourceType=" + + spMetaDataSourceType + ", spMetaDataFN=" + spMetaDataFN + ", spMetaDataURL=" + spMetaDataURL + + ", metaLocation=" + metaLocation + ", jansEntityId=" + jansEntityId + ", releasedAttributes=" + + releasedAttributes + ", url=" + url + ", spLogoutURL=" + spLogoutURL + ", status=" + status + + ", validationStatus=" + validationStatus + ", validationLog=" + validationLog + + ", profileConfigurations=" + profileConfigurations + "]"; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ValidationStatus.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ValidationStatus.java new file mode 100644 index 00000000000..386339c231a --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/ValidationStatus.java @@ -0,0 +1,56 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model; + +import java.util.HashMap; +import java.util.Map; + +import io.jans.orm.annotation.AttributeEnum; + + +public enum ValidationStatus implements AttributeEnum { + + PENDING("In Progress", "In Progress"), SUCCESS("Success", "Success"), SCHEDULED("Scheduled", + "Scheduled"), FAILED("Failed", "Failed"); + + private String value; + private String displayName; + + private static Map mapByValues = new HashMap(); + static { + for (ValidationStatus enumType : values()) { + mapByValues.put(enumType.getValue(), enumType); + } + } + + private ValidationStatus(String value, String displayName) { + this.value = value; + this.displayName = displayName; + } + + public String getValue() { + return value; + } + + public String getDisplayName() { + return displayName; + } + + public static ValidationStatus getByValue(String value) { + return mapByValues.get(value); + } + + public Enum resolveByValue(String value) { + return getByValue(value); + } + + @Override + public String toString() { + return value; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/IdpConfig.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/IdpConfig.java new file mode 100644 index 00000000000..4089826373a --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/IdpConfig.java @@ -0,0 +1,75 @@ +package io.jans.configapi.plugin.saml.model.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.io.Serializable; + + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IdpConfig implements Serializable { + + private static final long serialVersionUID = 3681304284933513886L; + + private String configId; + private String rootDir; + private boolean enabled; + private String metadataTempDir; + private String metadataDir; + private String metadataFilePattern; + + public String getConfigId() { + return configId; + } + + public void setConfigId(String configId) { + this.configId = configId; + } + + public String getRootDir() { + return rootDir; + } + + public void setRootDir(String rootDir) { + this.rootDir = rootDir; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getMetadataTempDir() { + return metadataTempDir; + } + + public void setMetadataTempDir(String metadataTempDir) { + this.metadataTempDir = metadataTempDir; + } + + public String getMetadataDir() { + return metadataDir; + } + + public void setMetadataDir(String metadataDir) { + this.metadataDir = metadataDir; + } + + public String getMetadataFilePattern() { + return metadataFilePattern; + } + + public void setMetadataFilePattern(String metadataFilePattern) { + this.metadataFilePattern = metadataFilePattern; + } + + + @Override + public String toString() { + return "IdpConfig [configId=" + configId + ", rootDir=" + rootDir + ", enabled=" + enabled + + ", metadataTempDir=" + metadataTempDir + ", metadataDir=" + metadataDir + ", metadataFilePattern=" + + metadataFilePattern + "]"; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlAppConfiguration.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlAppConfiguration.java new file mode 100644 index 00000000000..ad64adca8a0 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlAppConfiguration.java @@ -0,0 +1,120 @@ +package io.jans.configapi.plugin.saml.model.config; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.jans.as.model.configuration.Configuration; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SamlAppConfiguration implements Configuration { + + private String applicationName; + private String samlTrustRelationshipDn; + private boolean samlEnabled; + private String selectedIdp; + private String idpRootDir; + private String idpMetadataFilePattern; + private String spMetadataFilePattern; + private String spMetadataFile; + private boolean configGeneration; + private boolean ignoreValidation; + + private List idpConfigs; + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getSamlTrustRelationshipDn() { + return samlTrustRelationshipDn; + } + + public void setSamlTrustRelationshipDn(String samlTrustRelationshipDn) { + this.samlTrustRelationshipDn = samlTrustRelationshipDn; + } + + public boolean isSamlEnabled() { + return samlEnabled; + } + + public void setSamlEnabled(boolean samlEnabled) { + this.samlEnabled = samlEnabled; + } + + public String getSelectedIdp() { + return selectedIdp; + } + + public void setSelectedIdp(String selectedIdp) { + this.selectedIdp = selectedIdp; + } + + public String getIdpRootDir() { + return idpRootDir; + } + + public void setIdpRootDir(String idpRootDir) { + this.idpRootDir = idpRootDir; + } + + public String getIdpMetadataFilePattern() { + return idpMetadataFilePattern; + } + + public void setIdpMetadataFilePattern(String idpMetadataFilePattern) { + this.idpMetadataFilePattern = idpMetadataFilePattern; + } + + public String getSpMetadataFilePattern() { + return spMetadataFilePattern; + } + + public void setSpMetadataFilePattern(String spMetadataFilePattern) { + this.spMetadataFilePattern = spMetadataFilePattern; + } + + public String getSpMetadataFile() { + return spMetadataFile; + } + + public void setSpMetadataFile(String spMetadataFile) { + this.spMetadataFile = spMetadataFile; + } + + public boolean isConfigGeneration() { + return configGeneration; + } + + public void setConfigGeneration(boolean configGeneration) { + this.configGeneration = configGeneration; + } + + public boolean isIgnoreValidation() { + return ignoreValidation; + } + + public void setIgnoreValidation(boolean ignoreValidation) { + this.ignoreValidation = ignoreValidation; + } + + public List getIdpConfigs() { + return idpConfigs; + } + + public void setIdpConfigs(List idpConfigs) { + this.idpConfigs = idpConfigs; + } + + @Override + public String toString() { + return "SamlAppConfiguration [applicationName=" + applicationName + ", samlTrustRelationshipDn=" + + samlTrustRelationshipDn + ", samlEnabled=" + samlEnabled + ", selectedIdp=" + selectedIdp + + ", idpRootDir=" + idpRootDir + ", idpMetadataFilePattern=" + idpMetadataFilePattern + + ", spMetadataFilePattern=" + spMetadataFilePattern + ", configGeneration=" + configGeneration + + ", ignoreValidation=" + ignoreValidation + ", idpConfigs=" + idpConfigs + "]"; + } +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConf.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConf.java new file mode 100644 index 00000000000..648345b1825 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConf.java @@ -0,0 +1,72 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.model.config; + +import io.jans.as.model.config.StaticConfiguration; +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DN; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.JsonObject; +import io.jans.orm.annotation.ObjectClass; + + +@DataEntry +@ObjectClass(value = "jansAppConf") +public class SamlConf { + @DN + private String dn; + + @JsonObject + @AttributeName(name = "jansConfDyn") + private SamlAppConfiguration dynamicConf; + + @JsonObject + @AttributeName(name = "jansConfStatic") + private StaticConfiguration staticsConf; + + @AttributeName(name = "jansRevision") + private long revision; + + public String getDn() { + return dn; + } + + public void setDn(String dn) { + this.dn = dn; + } + + public SamlAppConfiguration getDynamicConf() { + return dynamicConf; + } + + public void setDynamicConf(SamlAppConfiguration dynamicConf) { + this.dynamicConf = dynamicConf; + } + + public StaticConfiguration getStaticsConf() { + return staticsConf; + } + + public void setStaticsConf(StaticConfiguration staticsConf) { + this.staticsConf = staticsConf; + } + + public long getRevision() { + return revision; + } + + public void setRevision(long revision) { + this.revision = revision; + } + + @Override + public String toString() { + return "SamlConf [dn=" + dn + ", dynamicConf=" + dynamicConf + ", staticsConf=" + staticsConf + ", revision=" + + revision + "]"; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConfigSource.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConfigSource.java new file mode 100644 index 00000000000..fe67304285f --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/model/config/SamlConfigSource.java @@ -0,0 +1,81 @@ +package io.jans.configapi.plugin.saml.model.config; + +import io.jans.exception.ConfigurationException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class SamlConfigSource implements ConfigSource { + + private static Logger log = LoggerFactory.getLogger(SamlConfigSource.class); + private static final String FILE_CONFIG = "saml.properties"; + private Properties properties = null; + Map propertiesMap = new HashMap<>(); + + public SamlConfigSource() { + this.loadProperties(); + } + + @Override + public Map getProperties() { + log.debug("Getting properties"); + return propertiesMap; + } + + @Override + public Set getPropertyNames() { + log.debug("Getting Property Names"); + try { + return properties.stringPropertyNames(); + + } catch (Exception e) { + log.error("Unable to read properties from file: " + FILE_CONFIG, e); + } + return Collections.emptySet(); + } + + @Override + public int getOrdinal() { + return 800; + } + + @Override + public String getValue(String name) { + log.debug("SamlConfigSource()::getValue() - name:{}", name); + try { + return properties.getProperty(name); + } catch (Exception e) { + log.error("Unable to read properties from file: " + FILE_CONFIG, e); + } + + return null; + } + + @Override + public String getName() { + return FILE_CONFIG; + } + + private Properties loadProperties() { + // Load the properties file + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try ( InputStream inputStream = loader.getResourceAsStream(FILE_CONFIG)) { + properties = new Properties(); + properties.load(inputStream); + properties.stringPropertyNames().stream().forEach(key -> propertiesMap.put(key, properties.getProperty(key))); + return properties; + } catch (Exception e) { + throw new ConfigurationException("Failed to load configuration from "+ FILE_CONFIG, e); + } + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/ApiApplication.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/ApiApplication.java new file mode 100644 index 00000000000..d69e261b0a3 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/ApiApplication.java @@ -0,0 +1,44 @@ +package io.jans.configapi.plugin.saml.rest; + +import io.jans.configapi.plugin.saml.util.Constants; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.*; +import io.swagger.v3.oas.annotations.tags.*; +import io.swagger.v3.oas.annotations.security.*; +import io.swagger.v3.oas.annotations.servers.*; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; +import java.util.HashSet; +import java.util.Set; + +@ApplicationPath("/saml") +@OpenAPIDefinition(info = @Info(title = "Jans Config API - SAML", version = "1.0.0", contact = @Contact(name = "Gluu Support", url = "https://support.gluu.org", email = "xxx@gluu.org"), + +license = @License(name = "Apache 2.0", url = "https://github.com/JanssenProject/jans/blob/main/LICENSE")), + +tags = { @Tag(name = "SAML - Configuration"), +@Tag(name = "SAML - Trust Relationship")}, + +servers = { @Server(url = "https://jans.io/", description = "The Jans server") }) + +@SecurityScheme(name = "oauth2", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows(clientCredentials = @OAuthFlow(tokenUrl = "https://{op-hostname}/.../token", scopes = { +@OAuthScope(name = Constants.SAML_READ_ACCESS, description = "View SAML related information"), +@OAuthScope(name = Constants.SAML_WRITE_ACCESS, description = "Manage SAML related information"), +@OAuthScope(name = Constants.SAML_CONFIG_READ_ACCESS, description = "View SAML configuration related information"), +@OAuthScope(name = Constants.SAML_CONFIG_WRITE_ACCESS, description = "Manage SAML configuration related information") +} +))) +public class ApiApplication extends Application { + + @Override + public Set> getClasses() { + HashSet> classes = new HashSet<>(); + + classes.add(SamlConfigResource.class); + classes.add(TrustRelationshipResource.class); + + return classes; + } +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/SamlConfigResource.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/SamlConfigResource.java new file mode 100644 index 00000000000..a91b48a9ea4 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/SamlConfigResource.java @@ -0,0 +1,113 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.rest; + +import io.jans.configapi.core.rest.BaseResource; +import io.jans.configapi.core.rest.ProtectedApi; +import io.jans.configapi.core.util.Jackson; +import io.jans.configapi.plugin.saml.model.config.SamlAppConfiguration; +import io.jans.configapi.plugin.saml.model.config.SamlConf; +import io.jans.configapi.plugin.saml.service.SamlConfigService; +import io.jans.configapi.plugin.saml.util.Constants; +import io.jans.configapi.util.ApiAccessConstants; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.*; + +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import java.io.IOException; + +import org.slf4j.Logger; + +import com.github.fge.jsonpatch.JsonPatch; +import com.github.fge.jsonpatch.JsonPatchException; + +@Path(Constants.SAML_CONFIG) +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class SamlConfigResource extends BaseResource { + + @Inject + Logger logger; + + @Inject + SamlConfigService samlConfigService; + + @Operation(summary = "Gets SAML configuration properties", description = "Gets SAML configuration properties", operationId = "get-saml-properties", tags = { + "SAML - Configuration" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_CONFIG_READ_ACCESS })) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SamlAppConfiguration.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @GET + @ProtectedApi(scopes = { Constants.SAML_CONFIG_READ_ACCESS }, groupScopes = { + Constants.SAML_CONFIG_WRITE_ACCESS }, superScopes = { ApiAccessConstants.SUPER_ADMIN_READ_ACCESS, + ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS }) + public Response getSamlConfiguration() { + SamlAppConfiguration samlConfiguration = samlConfigService.find(); + logger.info("SAML details samlConfiguration():{}", samlConfiguration); + return Response.ok(samlConfiguration).build(); + } + + @Operation(summary = "Update SAML configuration properties", description = "Update SAML configuration properties", operationId = "put-saml-properties", tags = { + "SAML - Configuration" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_CONFIG_WRITE_ACCESS })) + @RequestBody(description = "GluuAttribute object", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SamlAppConfiguration.class), examples = @ExampleObject(name = "Request example", value = "example/saml/config/saml-put.json"))) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SamlAppConfiguration.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @PUT @ProtectedApi(scopes = { Constants.SAML_CONFIG_WRITE_ACCESS }, groupScopes = {}, superScopes = { + ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS }) + public Response updateSamlConfiguration(@Valid SamlAppConfiguration samlConfiguration) { + logger.info("Update SAML details samlConfiguration():{}", samlConfiguration); + SamlConf conf = samlConfigService.findSamlConf(); + conf.setDynamicConf(samlConfiguration); + samlConfigService.mergeSamlConfig(conf); + samlConfiguration = samlConfigService.find(); + logger.info("SAML post update - samlConfiguration:{}", samlConfiguration); + return Response.ok(samlConfiguration).build(); + + } + + @Operation(summary = "Partially modifies SAML configuration properties.", description = "Partially modifies SAML Configuration properties.", operationId = "patch-saml-properties", tags = { + "SAML - Configuration" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_CONFIG_WRITE_ACCESS })) + @RequestBody(description = "String representing patch-document.", content = @Content(mediaType = MediaType.APPLICATION_JSON_PATCH_JSON, array = @ArraySchema(schema = @Schema(implementation = JsonPatch.class)), examples = @ExampleObject(name = "Request json example", value = "example/saml/config/saml-patch.json"))) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = SamlAppConfiguration.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @PATCH + @Consumes(MediaType.APPLICATION_JSON_PATCH_JSON) + @ProtectedApi(scopes = { Constants.SAML_CONFIG_WRITE_ACCESS }, groupScopes = {}, superScopes = { + ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS }) + public Response patchSamlConfiguration(@NotNull String jsonPatchString) throws JsonPatchException, IOException { + logger.info("Config API - jsonPatchString:{} ", jsonPatchString); + SamlConf conf = samlConfigService.findSamlConf(); + SamlAppConfiguration samlConfiguration = Jackson.applyPatch(jsonPatchString, conf.getDynamicConf()); + conf.setDynamicConf(samlConfiguration); + samlConfigService.mergeSamlConfig(conf); + samlConfiguration = samlConfigService.find(); + logger.info("SAML post patch - samlConfiguration:{}", samlConfiguration); + return Response.ok(samlConfiguration).build(); + } +} \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/TrustRelationshipResource.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/TrustRelationshipResource.java new file mode 100644 index 00000000000..4d8edb5bd13 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/rest/TrustRelationshipResource.java @@ -0,0 +1,190 @@ +package io.jans.configapi.plugin.saml.rest; + +import static io.jans.as.model.util.Util.escapeLog; +import io.jans.configapi.plugin.saml.model.TrustRelationship; +import io.jans.configapi.plugin.saml.form.TrustRelationshipForm; +import io.jans.configapi.core.rest.BaseResource; +import io.jans.configapi.core.rest.ProtectedApi; +import io.jans.configapi.plugin.saml.util.Constants; +import io.jans.configapi.util.AttributeNames; +import io.jans.configapi.plugin.saml.service.SamlService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.*; + +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; + +import java.io.InputStream; +import java.io.IOException; +import java.util.*; + +import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; +import org.slf4j.Logger; + +@Path(Constants.SAML_TRUST_RELATIONSHIP) +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class TrustRelationshipResource extends BaseResource { + + private static final String SAML_TRUST_RELATIONSHIP = "Trust Relationship"; + private static final String SAML_TRUST_RELATIONSHIP_FORM = "Trust Relationship From"; + + @Inject + Logger logger; + + @Inject + SamlService samlService; + + @Operation(summary = "Get all Trust Relationship", description = "Get all TrustRelationship.", operationId = "get-trust-relationship", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_READ_ACCESS })) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, array = @ArraySchema(schema = @Schema(implementation = TrustRelationship.class)))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @GET + @ProtectedApi(scopes = { Constants.SAML_READ_ACCESS }) + public Response getAllTrustRelationship() { + List trustRelationshipList = samlService.getAllTrustRelationships(); + logger.info("All trustRelationshipList:{}", trustRelationshipList); + return Response.ok(trustRelationshipList).build(); + } + + @Operation(summary = "Get TrustRelationship by Id", description = "Get TrustRelationship by Id", operationId = "get-trust-relationship-by-id", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_READ_ACCESS })) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TrustRelationship.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @GET + @ProtectedApi(scopes = { Constants.SAML_READ_ACCESS }) + @Path(Constants.ID_PATH + Constants.ID_PATH_PARAM) + public Response getTrustRelationshipById( + @Parameter(description = "Unique identifier - Id") @PathParam(Constants.ID) @NotNull String id) { + logger.info("Searching TrustRelationship by id: {}", escapeLog(id)); + + TrustRelationship trustRelationship = samlService.getTrustRelationshipByInum(id); + + logger.info("TrustRelationship found by id:{}, trustRelationship:{}", id, trustRelationship); + + return Response.ok(trustRelationship).build(); + } + + @Operation(summary = "Create Trust Relationship with Metadata File", description = "Create Trust Relationship with Metadata File", operationId = "post-trust-relationship-metadata-file", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_WRITE_ACCESS })) + @RequestBody(description = "Trust Relationship object", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TrustRelationship.class), examples = @ExampleObject(name = "Request example", value = "example/trust-relationship/trust-relationship-post.json"))) + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Newly created Trust Relationship", content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA, schema = @Schema(implementation = TrustRelationshipForm.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Path("/upload") + @ProtectedApi(scopes = { Constants.SAML_WRITE_ACCESS }, groupScopes = {}, superScopes = { + Constants.SAML_WRITE_ACCESS }) + @POST + public Response createTrustRelationshipWithFile(@MultipartForm TrustRelationshipForm trustRelationshipForm, + InputStream metadatafile) throws IOException { + logger.info(" Create trustRelationshipForm:{} ", trustRelationshipForm); + checkResourceNotNull(trustRelationshipForm, SAML_TRUST_RELATIONSHIP_FORM); + + TrustRelationship trustRelationship = trustRelationshipForm.getTrustRelationship(); + logger.debug(" Create trustRelationship:{} ", trustRelationship); + checkResourceNotNull(trustRelationshipForm.getTrustRelationship(), SAML_TRUST_RELATIONSHIP); + checkNotNull(trustRelationshipForm.getTrustRelationship().getClientId(), AttributeNames.NAME); + + InputStream metaDataFile = trustRelationshipForm.getMetaDataFile(); + logger.debug(" Create metaDataFile:{} ", metaDataFile); + if (metaDataFile != null) { + logger.debug(" Create metaDataFile.available():{}", metaDataFile.available()); + } + + // TO-DO validation of TrustRelationship + String inum = samlService.generateInumForNewRelationship(); + trustRelationship.setInum(inum); + trustRelationship.setDn(samlService.getDnForTrustRelationship(inum)); + + trustRelationship = samlService.addTrustRelationship(trustRelationship, metaDataFile); + + logger.info("Create created by TrustRelationship:{}", trustRelationship); + return Response.status(Response.Status.CREATED).entity(trustRelationship).build(); + } + + @Operation(summary = "Update TrustRelationship", description = "Update TrustRelationship", operationId = "put-trust-relationship", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_WRITE_ACCESS })) + @RequestBody(description = "Trust Relationship object", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TrustRelationship.class), examples = @ExampleObject(name = "Request example", value = "example/trust-relationship/trust-relationship-put.json"))) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = TrustRelationship.class))), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @ProtectedApi(scopes = { Constants.SAML_WRITE_ACCESS }) + @PUT + public Response updateTrustRelationship(@Valid TrustRelationship trustRelationship) throws IOException { + + logger.info("Update trustRelationship:{}", trustRelationship); + + // TO-DO validation of TrustRelationship + trustRelationship = samlService.updateTrustRelationship(trustRelationship); + + logger.info("Post update trustRelationship:{}", trustRelationship); + + return Response.ok(trustRelationship).build(); + } + + @Operation(summary = "Delete TrustRelationship", description = "Delete TrustRelationship", operationId = "put-trust-relationship", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_WRITE_ACCESS })) + @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "No Content"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @Path(Constants.ID_PATH_PARAM) + @ProtectedApi(scopes = { Constants.SAML_WRITE_ACCESS }) + @DELETE + public Response deleteTrustRelationship( + @Parameter(description = "Unique Id of Trust Relationship") @PathParam(Constants.ID) @NotNull String id) { + + logger.info("Delete client identified by id:{}", escapeLog(id)); + + TrustRelationship trustRelationship = samlService.getTrustRelationshipByInum(id); + if (trustRelationship == null) { + checkResourceNotNull(trustRelationship, SAML_TRUST_RELATIONSHIP); + } + samlService.removeTrustRelationship(trustRelationship); + + return Response.noContent().build(); + } + + @Operation(summary = "Process unprocessed metadata files", description = "Process unprocessed metadata files", operationId = "post-metadata-files", tags = { + "SAML - Trust Relationship" }, security = @SecurityRequirement(name = "oauth2", scopes = { + Constants.SAML_WRITE_ACCESS })) + @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "500", description = "InternalServerError") }) + @Path(Constants.PROCESS_META_FILE) + @ProtectedApi(scopes = { Constants.SAML_WRITE_ACCESS }) + @POST + public Response processMetadataFiles() { + + logger.info("process metadata files"); + + samlService.processUnprocessedMetadataFiles(); + + return Response.ok().build(); + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlConfigService.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlConfigService.java new file mode 100644 index 00000000000..2a7ba05f19f --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlConfigService.java @@ -0,0 +1,187 @@ +package io.jans.configapi.plugin.saml.service; + +import io.jans.as.common.service.common.ApplicationFactory; +import io.jans.configapi.plugin.saml.configuration.SamlConfigurationFactory; +import io.jans.configapi.plugin.saml.model.config.SamlAppConfiguration; +import io.jans.configapi.plugin.saml.model.config.IdpConfig; +import io.jans.configapi.plugin.saml.model.config.SamlConf; +import io.jans.orm.PersistenceEntryManager; +import io.jans.util.exception.InvalidConfigurationException; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; + +@ApplicationScoped +public class SamlConfigService { + + @Inject + Logger logger; + + @Inject + @Named(ApplicationFactory.PERSISTENCE_ENTRY_MANAGER_NAME) + PersistenceEntryManager persistenceManager; + + @Inject + SamlConfigurationFactory samlConfigurationFactory; + + public SamlConf findSamlConf() { + final String dn = samlConfigurationFactory.getSamlConfigurationDn(); + if (StringUtils.isBlank(dn)) { + throw new InvalidConfigurationException("Saml Configuration DN is undefined!"); + } + + logger.info(" dn:{}", dn); + SamlConf samlConf = persistenceManager.find(dn, SamlConf.class, null); + logger.info(" samlConf:{}", samlConf); + + return samlConf; + } + + public void mergeSamlConfig(SamlConf samlConf) { + samlConf.setRevision(samlConf.getRevision() + 1); + persistenceManager.merge(samlConf); + } + + public SamlAppConfiguration find() { + return getSamlConf().getDynamicConf(); + } + + public String getSelectedIdp() { + final SamlConf samlConf = getSamlConf(); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + String selectedIdp = null; + if (samlAppConfiguration != null) { + selectedIdp = samlAppConfiguration.getSelectedIdp(); + } + return selectedIdp; + } + + public boolean isSamlEnabled() { + final SamlConf samlConf = getSamlConf(); + logger.debug("samlConf.getDynamicConf():{}", samlConf.getDynamicConf()); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + boolean isSamlEnabled = false; + if (samlAppConfiguration != null) { + isSamlEnabled = samlAppConfiguration.isSamlEnabled(); + } + return isSamlEnabled; + } + + public String getIdpRootDir() { + final SamlConf samlConf = getSamlConf(); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + String idpRootDir = null; + if (samlAppConfiguration != null) { + idpRootDir = samlAppConfiguration.getIdpRootDir(); + } + return idpRootDir; + } + + public String getTrustRelationshipDn() { + final SamlConf samlConf = getSamlConf(); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + String trustRelationshipDn = null; + if (samlAppConfiguration != null) { + trustRelationshipDn = samlAppConfiguration.getSamlTrustRelationshipDn(); + } + return trustRelationshipDn; + } + + public String getSpMetadataFilePattern() { + final SamlConf samlConf = getSamlConf(); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + String spMetadataFilePattern = null; + if (samlAppConfiguration != null) { + spMetadataFilePattern = samlAppConfiguration.getSpMetadataFilePattern(); + } + return spMetadataFilePattern; + } + + public String getSpMetadataFile() { + final SamlConf samlConf = getSamlConf(); + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + String spMetadataFile = null; + if (samlAppConfiguration != null) { + spMetadataFile = samlAppConfiguration.getSpMetadataFile(); + } + return spMetadataFile; + } + + public IdpConfig getSelectedIdpConfig() { + final SamlConf samlConf = getSamlConf(); + + SamlAppConfiguration samlAppConfiguration = samlConf.getDynamicConf(); + IdpConfig selectedIdpConfig = null; + if (samlAppConfiguration != null) { + String selectedIdp = samlAppConfiguration.getSelectedIdp(); + List idpConfigs = samlAppConfiguration.getIdpConfigs(); + if (idpConfigs == null || idpConfigs.isEmpty()) { + return selectedIdpConfig; + } + selectedIdpConfig = idpConfigs.stream() + .filter(e -> e.getConfigId() != null && e.getConfigId().equalsIgnoreCase(selectedIdp)).findAny().orElse(null); + } + return selectedIdpConfig; + } + + public String getSelectedIdpConfigRootDir() { + String rootDir = null; + IdpConfig selectedIdpConfig = getSelectedIdpConfig(); + + if (selectedIdpConfig == null) { + return rootDir; + } + + rootDir = selectedIdpConfig.getRootDir(); + return rootDir; + } + + public String getSelectedIdpConfigMetadataTempDir() { + String idpTempMetadataFolder = null; + IdpConfig selectedIdpConfig = getSelectedIdpConfig(); + + if (selectedIdpConfig == null) { + return idpTempMetadataFolder; + } + + idpTempMetadataFolder = selectedIdpConfig.getMetadataTempDir(); + return idpTempMetadataFolder; + } + + public String getSelectedIdpConfigMetadataDir() { + String metadataDir = null; + IdpConfig selectedIdpConfig = getSelectedIdpConfig(); + + if (selectedIdpConfig == null) { + return metadataDir; + } + + metadataDir = selectedIdpConfig.getMetadataDir(); + return metadataDir; + } + + public String getSelectedIdpConfigID() { + String configId = null; + IdpConfig selectedIdpConfig = getSelectedIdpConfig(); + + if (selectedIdpConfig == null) { + return configId; + } + + configId = selectedIdpConfig.getConfigId(); + return configId; + } + + private SamlConf getSamlConf() { + SamlConf samlConf = findSamlConf(); + if (samlConf == null) { + throw new InvalidConfigurationException("SamlConf is undefined!"); + } + logger.debug(" samlConf:{}, samlConf.getDynamicConf():{}", samlConf, samlConf.getDynamicConf()); + return samlConf; + } +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlIdpService.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlIdpService.java new file mode 100644 index 00000000000..ae5afc2ff6b --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlIdpService.java @@ -0,0 +1,185 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.service; + +import io.jans.configapi.plugin.saml.model.TrustRelationship; +import io.jans.service.document.store.service.DocumentStoreService; +import io.jans.service.document.store.conf.DocumentStoreType; +import io.jans.service.document.store.service.LocalDocumentStoreService; +import io.jans.util.exception.InvalidConfigurationException; +import io.jans.util.StringHelper; +import io.jans.util.INumGenerator; +import io.jans.xml.GluuErrorHandler; +import io.jans.xml.XMLValidator; + +import javax.xml.parsers.ParserConfigurationException; +import org.xml.sax.SAXException; +import org.opensaml.saml.common.xml.SAMLSchemaBuilder; +import org.opensaml.saml.common.xml.SAMLSchemaBuilder.SAML1Version; +import org.opensaml.xml.parse.XMLParserException; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import javax.xml.validation.Schema; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.util.*; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; + +@ApplicationScoped +public class SamlIdpService { + + @Inject + Logger logger; + + @Inject + SamlConfigService samlConfigService; + + @Inject + private DocumentStoreService documentStoreService; + + @Inject + private LocalDocumentStoreService localDocumentStoreService; + + private Schema samlSchema; + + @PostConstruct + public void create() { + SAMLSchemaBuilder samlSchemaBuilder = new SAMLSchemaBuilder(SAML1Version.SAML_11); + try { + this.samlSchema = samlSchemaBuilder.getSAMLSchema(); + logger.debug("samlSchema", samlSchema); + } catch (Exception ex) { + logger.error("Failed to load SAMLSchema - ", ex); + } + } + + public boolean isLocalDocumentStoreType() { + return documentStoreService.getProviderType() == DocumentStoreType.LOCAL; + } + + public String getSpMetadataFilePath(String spMetaDataFN) { + if (StringUtils.isBlank(getIdpMetadataDir())) { + throw new InvalidConfigurationException("Failed to return IDP metadata file path as undefined!"); + } + + String idpMetadataFolder = getIdpMetadataDir(); + return idpMetadataFolder + spMetaDataFN; + } + + public String getIdpMetadataDir() { + if (StringUtils.isBlank(samlConfigService.getSelectedIdpConfigMetadataDir())) { + throw new InvalidConfigurationException("Failed to return IDP metadata file path as undefined!"); + } + return samlConfigService.getSelectedIdpConfigMetadataDir() + File.separator; + } + + public String getSpMetadataFile() { + if (StringUtils.isBlank(samlConfigService.getSpMetadataFile())) { + throw new InvalidConfigurationException("Failed to return IDP SP metadata file name as undefined!"); + } + return samlConfigService.getSpMetadataFile(); + } + + public String getSpNewMetadataFileName(TrustRelationship trustRel) { + return getSpNewMetadataFileName(trustRel.getInum()); + } + + public String getSpNewMetadataFileName(String inum) { + String relationshipInum = StringHelper.removePunctuation(inum); + return String.format(samlConfigService.getSpMetadataFilePattern(), relationshipInum); + } + + public String getIdpMetadataTempDir() { + if (StringUtils.isBlank(samlConfigService.getSelectedIdpConfigMetadataTempDir())) { + throw new InvalidConfigurationException("Failed to return IDP metadata Temp directory as undefined!"); + } + + return samlConfigService.getSelectedIdpConfigMetadataTempDir() + File.separator; + } + + private String getTempMetadataFilename(String idpMetadataFolder, String fileName) { + logger.info("documentStoreService:{}, localDocumentStoreService:{}, idpMetadataFolder:{}, fileName:{}", + documentStoreService, localDocumentStoreService, idpMetadataFolder, fileName); + synchronized (SamlIdpService.class) { + String possibleTemp; + do { + possibleTemp = fileName + INumGenerator.generate(2); + logger.debug("possibleTemp:{}", possibleTemp); + } while (documentStoreService.hasDocument(idpMetadataFolder + possibleTemp)); + return possibleTemp; + } + } + + public String saveSpMetadataFile(String spMetadataFileName, InputStream stream) { + logger.info("spMetadataFileName:{}, stream:{}", spMetadataFileName, stream); + + if (StringUtils.isBlank(samlConfigService.getSelectedIdpConfigRootDir())) { + throw new InvalidConfigurationException("Failed to save SP metadata file due to undefined!"); + } + + String idpMetadataTempFolder = getIdpMetadataTempDir(); + logger.debug("idpMetadataTempFolder:{}", idpMetadataTempFolder); + + String tempFileName = getTempMetadataFilename(idpMetadataTempFolder, spMetadataFileName); + logger.debug("idpMetadataTempFolder:{}, tempFileName:{}", idpMetadataTempFolder, tempFileName); + + String spMetadataFile = idpMetadataTempFolder + tempFileName; + logger.debug("documentStoreService:{}, spMetadataFile:{}, localDocumentStoreService:{} ", documentStoreService, + spMetadataFile, localDocumentStoreService); + try { + boolean result = documentStoreService.saveDocumentStream(spMetadataFile, stream, + List.of("jans-server", samlConfigService.getSelectedIdpConfigID())); + logger.debug("SP File saving result:{}", result); + + InputStream newFile = documentStoreService.readDocumentAsStream(spMetadataFile); + logger.debug("SP File read newFile:{}", newFile); + + if (result) { + return tempFileName; + } + } catch (Exception ex) { + logger.error("Failed to write SP metadata file '{}'", spMetadataFile, ex); + } finally { + IOUtils.closeQuietly(stream); + } + + return null; + } + + public GluuErrorHandler validateMetadata(String metadataPath) + throws ParserConfigurationException, SAXException, IOException, XMLParserException { + if (samlSchema == null) { + final List validationLog = new ArrayList(); + validationLog.add(GluuErrorHandler.SCHEMA_CREATING_ERROR_MESSAGE); + validationLog.add("Failed to load SAML schema"); + return new GluuErrorHandler(false, true, validationLog); + } + + try (InputStream stream = documentStoreService.readDocumentAsStream(metadataPath)) { + return XMLValidator.validateMetadata(stream, samlSchema); + } + } + + public boolean renameMetadata(String metadataPath, String destinationMetadataPath) { + logger.debug("Rename metadata file documentStoreService:{},metadataPath:{}, destinationMetadataPath:{}", documentStoreService, metadataPath, destinationMetadataPath); + try { + return documentStoreService.renameDocument(metadataPath, destinationMetadataPath); + } catch (Exception ex) { + logger.error("Failed to rename metadata '{}' to '{}'", metadataPath, destinationMetadataPath, ex); + } + + return false; + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlService.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlService.java new file mode 100644 index 00000000000..d90b37be8a6 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/service/SamlService.java @@ -0,0 +1,356 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.service; + +import io.jans.as.common.model.registration.Client; +import io.jans.as.common.service.common.InumService; +import io.jans.as.common.service.OrganizationService; +import io.jans.as.common.util.AttributeConstants; +import io.jans.configapi.configuration.ConfigurationFactory; +import io.jans.configapi.plugin.saml.timer.MetadataValidationTimer; +import io.jans.configapi.plugin.saml.model.TrustRelationship; + +import io.jans.model.GluuStatus; +import io.jans.model.SearchRequest; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.model.PagedResult; +import io.jans.orm.model.SortOrder; +import io.jans.orm.search.filter.Filter; +import io.jans.util.StringHelper; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.AgeFileFilter; +import org.apache.commons.io.filefilter.TrueFileFilter; +import org.slf4j.Logger; + +@ApplicationScoped +public class SamlService { + + @Inject + Logger log; + + @Inject + PersistenceEntryManager persistenceEntryManager; + + @Inject + ConfigurationFactory configurationFactory; + + @Inject + OrganizationService organizationService; + + @Inject + private InumService inumService; + + @Inject + SamlConfigService samlConfigService; + + @Inject + SamlIdpService samlIdpService; + + @Inject + MetadataValidationTimer metadataValidationTimer; + + public String getTrustRelationshipDn() { + return samlConfigService.getTrustRelationshipDn(); + } + + public String getSpMetadataFilePattern() { + return samlConfigService.getSpMetadataFilePattern(); + } + + public boolean containsRelationship(String dn) { + return persistenceEntryManager.contains(dn, TrustRelationship.class); + } + + public TrustRelationship getRelationshipByDn(String dn) { + if (StringHelper.isNotEmpty(dn)) { + try { + return persistenceEntryManager.find(TrustRelationship.class, dn); + } catch (Exception e) { + log.error(e.getMessage()); + } + + } + return null; + } + + public TrustRelationship getTrustRelationshipByInum(String inum) { + TrustRelationship result = null; + try { + result = persistenceEntryManager.find(TrustRelationship.class, getDnForTrustRelationship(inum)); + } catch (Exception ex) { + log.error("Failed to load TrustRelationship entry", ex); + } + return result; + } + + public List getAllTrustRelationships() { + return persistenceEntryManager.findEntries(getDnForTrustRelationship(null), TrustRelationship.class, null); + } + + public List getAllActiveTrustRelationships() { + TrustRelationship trustRelationship = new TrustRelationship(); + trustRelationship.setBaseDn(getDnForTrustRelationship(null)); + trustRelationship.setStatus(GluuStatus.ACTIVE); + + return persistenceEntryManager.findEntries(trustRelationship); + } + + public List getAllTrustRelationshipByInum(String inum) { + return persistenceEntryManager.findEntries(getDnForTrustRelationship(inum), TrustRelationship.class, null); + } + + public List getAllTrustRelationshipByName(String name) { + log.info("Search TrustRelationship with name:{}", name); + + String[] targetArray = new String[] { name }; + Filter displayNameFilter = Filter.createEqualityFilter(AttributeConstants.DISPLAY_NAME, targetArray); + log.debug("Search TrustRelationship with displayNameFilter:{}", displayNameFilter); + return persistenceEntryManager.findEntries(getDnForTrustRelationship(null), TrustRelationship.class, + displayNameFilter); + } + + public TrustRelationship getTrustContainerFederation(TrustRelationship trustRelationship) { + return getRelationshipByDn(trustRelationship.getDn()); + } + + public TrustRelationship getTrustContainerFederation(String dn) { + return getRelationshipByDn(dn); + } + + public List getAllTrustRelationships(int sizeLimit) { + return persistenceEntryManager.findEntries(getDnForTrustRelationship(null), TrustRelationship.class, null, + sizeLimit); + } + + public TrustRelationship getTrustByUnpunctuatedInum(String unpunctuated) { + for (TrustRelationship trust : getAllTrustRelationships()) { + if (StringHelper.removePunctuation(trust.getInum()).equals(unpunctuated)) { + return trust; + } + } + return null; + } + + public List searchTrustRelationship(String pattern, int sizeLimit) { + + log.info("Search TrustRelationship with pattern:{}, sizeLimit:{}", pattern, sizeLimit); + + String[] targetArray = new String[] { pattern }; + Filter displayNameFilter = Filter.createSubstringFilter(AttributeConstants.DISPLAY_NAME, null, targetArray, + null); + Filter descriptionFilter = Filter.createSubstringFilter(AttributeConstants.DESCRIPTION, null, targetArray, + null); + Filter inumFilter = Filter.createSubstringFilter(AttributeConstants.INUM, null, targetArray, null); + Filter searchFilter = Filter.createORFilter(displayNameFilter, descriptionFilter, inumFilter); + + log.info("Search TrustRelationship with searchFilter:{}", searchFilter); + return persistenceEntryManager.findEntries(getDnForTrustRelationship(null), TrustRelationship.class, + searchFilter, sizeLimit); + } + + public List getAllTrustRelationship(int sizeLimit) { + return persistenceEntryManager.findEntries(getDnForTrustRelationship(null), TrustRelationship.class, null, + sizeLimit); + } + + public PagedResult getTrustRelationship(SearchRequest searchRequest) { + log.info("Search TrustRelationship with searchRequest:{}", searchRequest); + + Filter searchFilter = null; + List filters = new ArrayList<>(); + if (searchRequest.getFilterAssertionValue() != null && !searchRequest.getFilterAssertionValue().isEmpty()) { + + for (String assertionValue : searchRequest.getFilterAssertionValue()) { + String[] targetArray = new String[] { assertionValue }; + Filter displayNameFilter = Filter.createSubstringFilter(AttributeConstants.DISPLAY_NAME, null, + targetArray, null); + Filter descriptionFilter = Filter.createSubstringFilter(AttributeConstants.DESCRIPTION, null, + targetArray, null); + Filter inumFilter = Filter.createSubstringFilter(AttributeConstants.INUM, null, targetArray, null); + filters.add(Filter.createORFilter(displayNameFilter, descriptionFilter, inumFilter)); + } + searchFilter = Filter.createORFilter(filters); + } + + log.debug("TrustRelationship pattern searchFilter:{}", searchFilter); + List fieldValueFilters = new ArrayList<>(); + if (searchRequest.getFieldValueMap() != null && !searchRequest.getFieldValueMap().isEmpty()) { + for (Map.Entry entry : searchRequest.getFieldValueMap().entrySet()) { + Filter dataFilter = Filter.createEqualityFilter(entry.getKey(), entry.getValue()); + log.trace("TrustRelationship dataFilter:{}", dataFilter); + fieldValueFilters.add(Filter.createANDFilter(dataFilter)); + } + searchFilter = Filter.createANDFilter(Filter.createORFilter(filters), + Filter.createANDFilter(fieldValueFilters)); + } + + log.info("TrustRelationship searchFilter:{}", searchFilter); + + return persistenceEntryManager.findPagedEntries(getDnForTrustRelationship(null), Client.class, searchFilter, + null, searchRequest.getSortBy(), SortOrder.getByValue(searchRequest.getSortOrder()), + searchRequest.getStartIndex(), searchRequest.getCount(), searchRequest.getMaxCount()); + + } + + public TrustRelationship addTrustRelationship(TrustRelationship trustRelationship) throws IOException { + return addTrustRelationship(trustRelationship, null); + } + + public TrustRelationship addTrustRelationship(TrustRelationship trustRelationship, InputStream file) + throws IOException { + log.info("Add new trustRelationship:{}, file:{}", trustRelationship, file); + + setTrustRelationshipDefaultValue(trustRelationship, false); + persistenceEntryManager.persist(trustRelationship); + + if (file != null && file.available() > 0) { + saveSpMetaDataFileSourceTypeFile(trustRelationship, file); + } + + return getTrustRelationshipByInum(trustRelationship.getInum()); + } + + public TrustRelationship updateTrustRelationship(TrustRelationship trustRelationship) throws IOException { + return updateTrustRelationship(trustRelationship, null); + } + + public TrustRelationship updateTrustRelationship(TrustRelationship trustRelationship, InputStream file) + throws IOException { + setTrustRelationshipDefaultValue(trustRelationship, true); + persistenceEntryManager.merge(trustRelationship); + + if (file != null && file.available() > 0) { + saveSpMetaDataFileSourceTypeFile(trustRelationship, file); + } + + return getTrustRelationshipByInum(trustRelationship.getInum()); + + } + + public void removeTrustRelationship(TrustRelationship trustRelationship) { + persistenceEntryManager.removeRecursively(trustRelationship.getDn(), TrustRelationship.class); + + } + + private TrustRelationship setTrustRelationshipDefaultValue(TrustRelationship trustRelationship, boolean update) { + return trustRelationship; + } + + public String getDnForTrustRelationship(String inum) { + String orgDn = organizationService.getDnForOrganization(); + if (StringHelper.isEmpty(inum)) { + return String.format("ou=trustRelationships,%s", orgDn); + } + return String.format("inum=%s,ou=trustRelationships,%s", inum, orgDn); + } + + public String generateInumForNewRelationship() { + String newInum = null; + String newDn = null; + do { + newInum = UUID.randomUUID().toString(); + newDn = getDnForTrustRelationship(newInum); + } while (containsRelationship(newDn)); + + return newInum; + } + + private boolean saveSpMetaDataFileSourceTypeFile(TrustRelationship trustRelationship, InputStream file) { + log.info("trustRelationship:{}, file:{}", trustRelationship, file); + + String spMetadataFileName = trustRelationship.getSpMetaDataFN(); + boolean emptySpMetadataFileName = StringHelper.isEmpty(spMetadataFileName); + log.debug("emptySpMetadataFileName:{}", emptySpMetadataFileName); + if ((file == null)) { + log.trace("File is null"); + if (emptySpMetadataFileName) { + log.debug("The trust relationship {} has an empty Metadata filename", trustRelationship.getInum()); + return false; + } + String filePath = samlIdpService.getSpMetadataFilePath(spMetadataFileName); + log.debug("filePath:{}", filePath); + + if (filePath == null) { + log.debug("The trust relationship {} has an invalid Metadata file storage path", + trustRelationship.getInum()); + return false; + } + + if (samlIdpService.isLocalDocumentStoreType()) { + + File newFile = new File(filePath); + log.debug("newFile:{}", newFile); + + if (!newFile.exists()) { + log.debug( + "The trust relationship {} metadata used local storage but the SP metadata file `{}` was not found", + trustRelationship.getInum(), filePath); + return false; + } + } + return true; + } + if (emptySpMetadataFileName) { + log.debug("emptySpMetadataFileName:{}", emptySpMetadataFileName); + spMetadataFileName = samlIdpService.getSpNewMetadataFileName(trustRelationship); + log.debug("spMetadataFileName:{}", spMetadataFileName); + trustRelationship.setSpMetaDataFN(spMetadataFileName); + + } + InputStream targetStream = file; + log.debug("targetStream:{}, spMetadataFileName:{}", targetStream, spMetadataFileName); + + String result = samlIdpService.saveSpMetadataFile(spMetadataFileName, targetStream); + log.debug("targetStream:{}, spMetadataFileName:{}", targetStream, spMetadataFileName); + if (StringHelper.isNotEmpty(result)) { + metadataValidationTimer.queue(result); + //process files in temp that were not processed earlier + processUnprocessedMetadataFiles(); + } else { + log.error("Failed to save SP meta-data file. Please check if you provide correct file"); + } + + return false; + + } + + public void processUnprocessedMetadataFiles() { + log.debug("processing unprocessed metadata files "); + String directory = samlIdpService.getIdpMetadataTempDir(); + log.debug("directory:{}, Files.exists(Paths.get(directory):{}", directory, Files.exists(Paths.get(directory))); + + if (Files.exists(Paths.get(directory))) { + log.debug("directory:{} does exists)", directory); + File folder = new File(directory); + File[] files = folder.listFiles(); + log.debug("files:{}", files); + if (files != null && files.length > 0) { + + for (File file : files) { + log.debug("file:{}, file.getName():{}", file, file.getName()); + metadataValidationTimer.queue(file.getName()); + } + } + + } + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/timer/MetadataValidationTimer.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/timer/MetadataValidationTimer.java new file mode 100644 index 00000000000..0a3b2a0b3b3 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/timer/MetadataValidationTimer.java @@ -0,0 +1,352 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.timer; + +import io.jans.configapi.plugin.saml.event.MetadataValidationEvent; +import io.jans.configapi.plugin.saml.model.config.SamlAppConfiguration; +import io.jans.configapi.plugin.saml.model.TrustRelationship; +import io.jans.configapi.plugin.saml.model.ValidationStatus; +import io.jans.configapi.plugin.saml.service.SamlService; +import io.jans.configapi.plugin.saml.service.SamlIdpService; +import io.jans.model.GluuStatus; +import io.jans.saml.metadata.SAMLMetadataParser; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; + +import io.jans.util.StringHelper; +import io.jans.xml.GluuErrorHandler; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Named; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; + +@ApplicationScoped +@Named +public class MetadataValidationTimer { + + private final static int DEFAULT_INTERVAL = 60; // 60 seconds + + @Inject + private Logger log; + + @Inject + private Event timerEvent; + + @Inject + private SamlAppConfiguration samlAppConfiguration; + + @Inject + private SamlService samlService; + + @Inject + private SAMLMetadataParser samlMetadataParser; + + @Inject + private SamlIdpService samlIdpService; + + private AtomicBoolean isActive; + + private LinkedBlockingQueue metadataUpdates; + + @PostConstruct + public void init() { + this.isActive = new AtomicBoolean(true); + try { + this.metadataUpdates = new LinkedBlockingQueue(); + } finally { + this.isActive.set(false); + } + } + + public void initTimer() { + log.debug("Initializing Metadata Validation Timer"); + + final int delay = 30; + final int interval = DEFAULT_INTERVAL; + + timerEvent.fire(new TimerEvent(new TimerSchedule(delay, interval), new MetadataValidationEvent(), + Scheduled.Literal.INSTANCE)); + } + + @Asynchronous + public void processMetadataValidationTimerEvent( + @Observes @Scheduled MetadataValidationEvent metadataValidationEvent) { + if (this.isActive.get()) { + return; + } + + if (!this.isActive.compareAndSet(false, true)) { + return; + } + + try { + procesMetadataValidation(); + } catch (Throwable ex) { + log.error("Exception happened while reloading application configuration", ex); + } finally { + this.isActive.set(false); + } + } + + private void procesMetadataValidation() throws Exception { + log.debug("Starting metadata validation"); + boolean result = validateMetadata(samlIdpService.getIdpMetadataTempDir(), samlIdpService.getIdpMetadataDir()); + log.debug("Metadata validation finished with result: '{}'", result); + + } + + public void queue(String fileName) { + synchronized (metadataUpdates) { + log.debug("fileNamem:{}, metadataUpdates.contains(fileName):{}", fileName, + metadataUpdates.contains(fileName)); + if (!metadataUpdates.contains(fileName)) { + metadataUpdates.add(fileName); + } + } + } + + public boolean isQueued(String samlspMetaDataFN) { + synchronized (metadataUpdates) { + for (String filename : metadataUpdates) { + if (filename.contains(samlspMetaDataFN)) { + return true; + } + } + return false; + } + } + + public String getValidationStatus(String samlspMetaDataFN, TrustRelationship trust) { + if (trust.getValidationStatus() == null) { + return ValidationStatus.SUCCESS.getDisplayName(); + } + if (trust.getValidationStatus() == null) { + return ValidationStatus.PENDING.getDisplayName(); + } + synchronized (metadataUpdates) { + boolean result = false; + for (String filename : metadataUpdates) { + if (filename.contains(samlspMetaDataFN)) { + result = true; + break; + } + } + if (result) { + return ValidationStatus.SCHEDULED.getDisplayName(); + } else { + return trust.getValidationStatus().getDisplayName(); + } + } + } + + /** + * @param tempmetadataFolder + * @param metadataFolder + */ + private boolean validateMetadata(String tempmetadataFolder, String metadataFolder) throws Exception { + boolean result = false; + log.debug("Starting metadata validation process."); + + String metadataFN = null; + synchronized (metadataUpdates) { + if (!metadataUpdates.isEmpty()) { + metadataFN = metadataUpdates.poll(); + + } + } + log.debug("metadataFN:{}", metadataFN); + + synchronized (this) { + if (metadataFN!=null && StringHelper.isNotEmpty(metadataFN)) { + String metadataPath = tempmetadataFolder + metadataFN; + String destinationMetadataName = metadataFN.replaceAll(".{4}\\..{4}$", ""); + String destinationMetadataPath = metadataFolder + destinationMetadataName; + log.debug("metadataFN:{}, metadataPath:{}, destinationMetadataName:{}, destinationMetadataPath:{}", + metadataFN, metadataPath, destinationMetadataName, destinationMetadataPath); + TrustRelationship tr = samlService + .getTrustByUnpunctuatedInum(metadataFN.split("-" + samlIdpService.getSpMetadataFile())[0]); + log.debug("TrustRelationship found with name:{} is:{}",metadataFN, tr); + if (tr == null) { + log.debug("No TrustRelationship found with name:{}",metadataFN); + metadataUpdates.add(metadataFN); + return false; + } + tr.setValidationStatus(ValidationStatus.PENDING); + samlService.updateTrustRelationship(tr); + + log.debug("metadataFN:{}, metadataPath:{}, destinationMetadataName:{}, destinationMetadataPath:{}", + metadataFN, metadataPath, destinationMetadataName, destinationMetadataPath); + + GluuErrorHandler errorHandler = null; + List validationLog = null; + try { + errorHandler = samlIdpService.validateMetadata(metadataPath); + log.debug("validateMetadata result errorHandler:{}", errorHandler); + } catch (Exception e) { + tr.setValidationStatus(ValidationStatus.FAILED); + tr.setStatus(GluuStatus.INACTIVE); + validationLog = this.getValidationLog(validationLog); + validationLog.add(e.getMessage()); + log.debug("Validation of " + tr.getInum() + " failed: " + e.getMessage()); + tr.setValidationLog(validationLog); + samlService.updateTrustRelationship(tr); + return false; + } + + if (errorHandler == null) { + return false; + } + log.debug( + "validateMetadata result errorHandler.isValid():{}, errorHandler.getLog():{}, errorHandler.toString():{}", + errorHandler.isValid(), errorHandler.getLog(), errorHandler.toString()); + log.debug("samlAppConfiguration.isIgnoreValidation():{} errorHandler.isInternalError():{}", + samlAppConfiguration.isIgnoreValidation(), errorHandler.isInternalError()); + + if (errorHandler.isValid()) { + log.debug("validate Metadata file processing"); + tr.setValidationLog(errorHandler.getLog()); + tr.setValidationStatus(ValidationStatus.SUCCESS); + + log.debug("Move metadata file:{} to location:{}", metadataPath, destinationMetadataPath); + boolean renamed = samlIdpService.renameMetadata(metadataPath, destinationMetadataPath); + + log.debug("Staus of moving file:{} to location:{} is :{}", metadataPath, destinationMetadataPath, + renamed); + + if (renamed) { + log.debug("Failed to move metadata file:{} to location:{}", metadataPath, + destinationMetadataPath); + tr.setStatus(GluuStatus.INACTIVE); + } else { + tr.setSpMetaDataFN(destinationMetadataName); + } + + String metadataFile = samlIdpService.getIdpMetadataDir() + tr.getSpMetaDataFN(); + log.debug("After successfully moving metadataFile :{}", metadataFile); + List entityIdList = samlMetadataParser.getEntityIdFromMetadataFile(metadataFile); + log.debug("Success entityIdList :{}", entityIdList); + Set entityIdSet = new TreeSet(); + Set duplicatesSet = new TreeSet(); + if (entityIdList != null && !entityIdList.isEmpty()) { + + for (String entityId : entityIdList) { + if (!entityIdSet.add(entityId)) { + duplicatesSet.add(entityId); + } + } + } + log.debug("Success duplicatesSet :{}", duplicatesSet); + if (!duplicatesSet.isEmpty()) { + validationLog = tr.getValidationLog(); + if (validationLog != null) { + validationLog = new LinkedList(validationLog); + } else { + validationLog = new LinkedList(); + } + validationLog.add("This metadata contains multiple instances of entityId: " + + Arrays.toString(duplicatesSet.toArray())); + } + tr.setValidationLog(validationLog); + tr.setStatus(GluuStatus.ACTIVE); + + samlService.updateTrustRelationship(tr); + result = true; + } else if (samlAppConfiguration.isIgnoreValidation() || errorHandler.isInternalError()) { + tr.setValidationLog(new ArrayList(new HashSet(errorHandler.getLog()))); + tr.setValidationStatus(ValidationStatus.FAILED); + boolean fileRenamed = samlIdpService.renameMetadata(metadataPath, destinationMetadataPath); + log.debug("Status of trustRelationship updated to Failed, File copy from:{} to:{}, status:()", + metadataPath, destinationMetadataPath, fileRenamed); + if (!fileRenamed) { + log.debug( + "Updating trustRelationship status to Inactive as metadata:{} could not be copied to:{}", + metadataPath, destinationMetadataPath); + tr.setStatus(GluuStatus.INACTIVE); + } else { + tr.setSpMetaDataFN(destinationMetadataName); + log.debug("Validation error for metadata file ignored as isIgnoreValidation:{}" + + samlAppConfiguration.isIgnoreValidation()); + } + + String metadataFile = samlIdpService.getIdpMetadataDir() + tr.getSpMetaDataFN(); + log.debug("metadataFile:{}", metadataFile); + + List entityIdList = samlMetadataParser.getEntityIdFromMetadataFile(metadataFile); + log.debug("entityIdList:{}", entityIdList); + Set duplicatesSet = new TreeSet(); + Set entityIdSet = new TreeSet(); + + if (entityIdList != null && !entityIdList.isEmpty()) { + for (String entityId : entityIdList) { + if (!entityIdSet.add(entityId)) { + duplicatesSet.add(entityId); + } + } + } + + tr.setStatus(GluuStatus.ACTIVE); + validationLog = tr.getValidationLog(); + log.debug("duplicatesSet:{}", duplicatesSet); + if (!duplicatesSet.isEmpty()) { + validationLog = this.getValidationLog(validationLog); + validationLog.add("This metadata contains multiple instances of entityId: " + + Arrays.toString(duplicatesSet.toArray())); + } + log.debug("errorHandler.isInternalError():{}", errorHandler.isInternalError()); + if (errorHandler.isInternalError()) { + validationLog = tr.getValidationLog(); + validationLog = this.getValidationLog(validationLog); + validationLog.add( + "Warning: cannot validate metadata. Check internet connetion ans www.w3.org availability."); + + log.debug("errorHandler.getLog():{}", errorHandler.getLog()); + // update log with warning + for (String warningLogMessage : errorHandler.getLog()) { + validationLog.add("Warning: " + warningLogMessage); + } + } + log.debug("Updating TrustRelationship:{} , validationLog :{}", tr, validationLog); + samlService.updateTrustRelationship(tr); + result = true; + } else { + log.debug("Unhandled metadataFN:{}", metadataFN); + tr.setValidationLog(new ArrayList(new HashSet(errorHandler.getLog()))); + tr.setValidationStatus(ValidationStatus.FAILED); + tr.setStatus(GluuStatus.INACTIVE); + samlService.updateTrustRelationship(tr); + } + } + } + + return result; + } + + private List getValidationLog(List validationLog) { + log.debug("validationLog:{}", validationLog); + if (validationLog == null) { + validationLog = new LinkedList(); + } + return validationLog; + } +} diff --git a/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/util/Constants.java b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/util/Constants.java new file mode 100644 index 00000000000..84491664f68 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/java/io/jans/configapi/plugin/saml/util/Constants.java @@ -0,0 +1,36 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi.plugin.saml.util; + +public class Constants { + + private Constants() {} + + public static final String SAML_CONFIG = "/samlConfig"; + public static final String SAML_TRUST_RELATIONSHIP = "/trust-relationship"; + public static final String SAML_SCOPE = "/scope"; + public static final String NAME_PARAM_PATH = "/{name}"; + public static final String NAME = "name"; + public static final String ID_PATH = "/id"; + public static final String ID_PATH_PARAM = "/{id}"; + public static final String ID = "id"; + public static final String CLIENTID_PATH = "/{clientId}"; + public static final String CLIENTID = "clientId"; + public static final String PROCESS_META_FILE = "/PROCESS_META_FILE"; + + + //Scopes + public static final String SAML_READ_ACCESS = "https://jans.io/oauth/config/saml.readonly"; + public static final String SAML_WRITE_ACCESS = "https://jans.io/oauth/config/saml.write"; + + public static final String SAML_CONFIG_READ_ACCESS = "https://jans.io/oauth/config/saml-config.readonly"; + public static final String SAML_CONFIG_WRITE_ACCESS = "https://jans.io/oauth/config/saml-config.write"; + + public static final String SAML_SCOPE_READ_ACCESS = "https://jans.io/oauth/config/saml-scope.readonly"; + public static final String SAML_SCOPE_WRITE_ACCESS = "https://jans.io/oauth/config/saml-scope.write"; + +} \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/beans.xml b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..bf2ab180c1c --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/beans.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 00000000000..9309b803015 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +io.jans.configapi.plugin.saml.extensions.SamlExtension \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers new file mode 100644 index 00000000000..b2c9664d366 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -0,0 +1,3 @@ +io.jans.configapi.filters.AuthorizationFilter +io.jans.configapi.filters.LoggingFilter + diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 00000000000..92739108cf7 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +io.jans.configapi.plugin.saml.model.config.SamlConfigSource \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/quartz.properties b/jans-config-api/plugins/saml-plugin/src/main/resources/quartz.properties new file mode 100644 index 00000000000..12cef917f4a --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/quartz.properties @@ -0,0 +1,4 @@ +org.quartz.scheduler.instanceName=JansConfigScheduler +org.quartz.threadPool.threadCount=5 +org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore +org.quartz.scheduler.skipUpdateCheck=true diff --git a/jans-config-api/plugins/saml-plugin/src/main/resources/saml.properties b/jans-config-api/plugins/saml-plugin/src/main/resources/saml.properties new file mode 100644 index 00000000000..940f5003369 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/main/resources/saml.properties @@ -0,0 +1,9 @@ +# Sample properties +saml.server.url=http://localhost:8180 +saml.realm.name= master +saml.client.id= https://samltest.id/saml/sp +saml.grant.type=PASSWORD +saml.admin.username=admin1 +saml.admin.password=admin123 +saml.client.id=jans-client-1 +saml.client.secret=yxsKi8ah9pU7ANyjH7HBwJh4XTLYN4x3 \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/KarateTestRunner.java b/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/KarateTestRunner.java new file mode 100644 index 00000000000..34da4586ef9 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/KarateTestRunner.java @@ -0,0 +1,18 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi; + +import com.intuit.karate.junit5.Karate; + +public class KarateTestRunner { + + @Karate.Test + Karate testFullPath() throws Exception { + return Karate.run("src/test/resources/feature"); + } + +} diff --git a/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/TestJenkinsRunner.java b/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/TestJenkinsRunner.java new file mode 100644 index 00000000000..a7f7d2d80c2 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/java/io/jans/configapi/TestJenkinsRunner.java @@ -0,0 +1,44 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.configapi; + +import com.intuit.karate.Results; +import com.intuit.karate.Runner; + +import io.jans.as.common.model.registration.Client; +import net.masterthought.cucumber.Configuration; +import net.masterthought.cucumber.ReportBuilder; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class TestJenkinsRunner { + + @Test + void testParallel() { + System.setProperty("karate.env", "jenkins"); + Results results = Runner.path("src/test/resources/feature").tags("~@ignore").parallel(1); + generateReport(results.getReportDir()); + Assertions.assertEquals(0, results.getFailCount(), results.getErrorMessages()); + } + + public static void generateReport(String karateOutputPath) { + Collection jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] { "json" }, true); + List jsonPaths = new ArrayList(jsonFiles.size()); + jsonFiles.forEach(file -> jsonPaths.add(file.getAbsolutePath())); + Configuration config = new Configuration(new File("target"), "karateTesting"); + ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config); + reportBuilder.generateReports(); + } +} diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/config/saml.feature b/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/config/saml.feature new file mode 100644 index 00000000000..9e6a9a52da2 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/config/saml.feature @@ -0,0 +1,21 @@ + +Feature: Verify SAML configuration endpoint + + Background:samlUrl + * def mainUrl = samlConfigUrl + + @get-saml-config-no-token + Scenario: Fetch SAML config without bearer token + Given url mainUrl + When method GET + Then status 401 + + @get-saml-config + Scenario: Retrieve SAML configuration + Given url mainUrl + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response + And assert response.length != null + diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/trust-relationship/saml-trust-relationship.feature b/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/trust-relationship/saml-trust-relationship.feature new file mode 100644 index 00000000000..515ced47946 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/feature/saml/trust-relationship/saml-trust-relationship.feature @@ -0,0 +1,21 @@ + +Feature: Verify SAML Trust Relationship endpoint + + Background:samlUrl + * def mainUrl = samlTrustRelationshipUrl + + @get-trust-relationship-no-token + Scenario: Fetch SAML Trust Relationships without bearer token + Given url mainUrl + When method GET + Then status 401 + + @get-trust-relationship + Scenario: Retrieve SAML Trust Relationship + Given url mainUrl + And header Authorization = 'Bearer ' + accessToken + When method GET + Then status 200 + And print response + And assert response.length != null + diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config-jenkins.js b/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config-jenkins.js new file mode 100644 index 00000000000..d32b307b93f --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config-jenkins.js @@ -0,0 +1,59 @@ +function() { + + var stream = read('classpath:karate_jenkins.properties'); + var props = new java.util.Properties(); + props.load(stream); + + var env = props.get('karate.env'); // get java system property 'karate.env' + karate.configure("ssl", true); + + if (!env) { + env = 'dev'; //env can be anything: dev, qa, staging, etc. + } + + var url = props.get('karate.test.url'); + var port = props.get('karate.test.port'); + var baseUrl = url + (port ? ':' + port : ''); + + karate.log('karate_jenkins env :', env); + karate.log('karate_jenkins url :', url); + karate.log('karate_jenkins port :', port); + karate.log('karate_jenkins baseUrl :', baseUrl); + + var testStream = read('classpath:test.properties'); + var testProps = new java.util.Properties(); + testProps.load(testStream); + karate.log(' testProps = '+testProps); + var testClientId = testProps.get('test.client.id'); + var testClientSecret = testProps.get('test.client.secret'); + var tokenEndpoint = testProps.get('token.endpoint'); + var testScopes = testProps.get('test.scopes'); + var issuer = testProps.get('test.issuer'); + karate.log(' testClientId = '+testClientId); + karate.log(' testClientSecret = '+testClientSecret); + karate.log(' tokenEndpoint = '+tokenEndpoint); + karate.log(' testScopes = '+testScopes); + karate.log(' issuer = '+issuer); + + + var config = { + env: env, + baseUrl: baseUrl, + testProps: testProps, + issuer: issuer, + accessToken: '123', + + samlTrustRelationshipUrl: baseUrl + '/jans-config-api/saml/trust-relationship', + samlConfigUrl: baseUrl + '/jans-config-api/saml/samlConfig', + + }; + + karate.configure('connectTimeout', 30000); + karate.configure('readTimeout', 60000); + + var result = karate.callSingle('classpath:token.feature', config); + print(' result.response = '+result.response); + config.accessToken = result.response.access_token; + + return config; +} \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config.js b/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config.js new file mode 100644 index 00000000000..a8086c01326 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/karate-config.js @@ -0,0 +1,58 @@ +function() { + + var stream = read('classpath:karate.properties'); + var props = new java.util.Properties(); + props.load(stream); + + var env = props.get('karate.env'); // get java system property 'karate.env' + karate.configure("ssl", true); + + if (!env) { + env = 'dev'; //env can be anything: dev, qa, staging, etc. + } + + var url = props.get('karate.test.url'); + var port = props.get('karate.test.port'); + var baseUrl = url + (port ? ':' + port : ''); + + karate.log('karate env :', env); + karate.log('karate url :', url); + karate.log('karate port :', port); + karate.log('karate baseUrl :', baseUrl); + + var testStream = read('classpath:test.properties'); + var testProps = new java.util.Properties(); + testProps.load(testStream); + karate.log(' testProps = '+testProps); + var testClientId = testProps.get('test.client.id'); + var testClientSecret = testProps.get('test.client.secret'); + var tokenEndpoint = testProps.get('token.endpoint'); + var testScopes = testProps.get('test.scopes'); + var issuer = testProps.get('test.issuer'); + karate.log(' testClientId = '+testClientId); + karate.log(' testClientSecret = '+testClientSecret); + karate.log(' tokenEndpoint = '+tokenEndpoint); + karate.log(' testScopes = '+testScopes); + karate.log(' issuer = '+issuer); + + + var config = { + env: env, + baseUrl: baseUrl, + testProps: testProps, + issuer: issuer, + accessToken: '123', + + samlTrustRelationshipUrl: baseUrl + '/jans-config-api/saml/trust-relationship', + samlConfigUrl: baseUrl + '/jans-config-api/saml/samlConfig', + }; + + karate.configure('connectTimeout', 30000); + karate.configure('readTimeout', 60000); + + var result = karate.callSingle('classpath:token.feature', config); + print(' result.response = '+result.response); + config.accessToken = result.response.access_token; + + return config; +} \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/karate.properties b/jans-config-api/plugins/saml-plugin/src/test/resources/karate.properties new file mode 100644 index 00000000000..41c0d369aff --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/karate.properties @@ -0,0 +1,5 @@ +#karate.test.url=http://localhost +#karate.test.port=8080 +#karate.test.url=https://jenkins-config-api.gluu.org/jans-config-api +#karate.test.port=443 +karate.test.url=${test.server} diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/karate_jenkins.properties b/jans-config-api/plugins/saml-plugin/src/test/resources/karate_jenkins.properties new file mode 100644 index 00000000000..0b44a8d7b13 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/karate_jenkins.properties @@ -0,0 +1,2 @@ +karate.test.url=${test.server} +#karate.test.port=443 diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/logback-test.xml b/jans-config-api/plugins/saml-plugin/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..fea195eb039 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/logback-test.xml @@ -0,0 +1,24 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + target/karate.log + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/test.properties b/jans-config-api/plugins/saml-plugin/src/test/resources/test.properties new file mode 100644 index 00000000000..4257f297907 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/test.properties @@ -0,0 +1,8 @@ +test.scopes=${test.scopes} + +# Test env Setting +token.endpoint=${token.endpoint} +token.grant.type=${token.grant.type} +test.client.id=${test.client.id} +test.client.secret=${test.client.secret} +test.issuer=${test.issuer} \ No newline at end of file diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/testClient.feature b/jans-config-api/plugins/saml-plugin/src/test/resources/testClient.feature new file mode 100644 index 00000000000..34cfdffc438 --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/testClient.feature @@ -0,0 +1,13 @@ +@ignore +Feature: This Feature is to get token to test the test cases + +Background: +* def mainUrl = test_url + +Scenario: Get Token +Given url mainUrl +And print url +And request '' +When method POST +Then status 204 +And print response diff --git a/jans-config-api/plugins/saml-plugin/src/test/resources/token.feature b/jans-config-api/plugins/saml-plugin/src/test/resources/token.feature new file mode 100644 index 00000000000..ef0ad0d262d --- /dev/null +++ b/jans-config-api/plugins/saml-plugin/src/test/resources/token.feature @@ -0,0 +1,45 @@ +@ignore +Feature: This Feature is to get token to test the test cases - Do not remove ignore tag + +Background: +* def mainUrl = testProps.get('token.endpoint'); +* def grantType = testProps.get('token.grant.type'); +* def clientId = testProps.get('test.client.id'); +* def clientSecret = testProps.get('test.client.secret'); +* def scopes = testProps.get('test.scopes'); +* def authStr = clientId+':'+clientSecret +* def Base64 = Java.type('java.util.Base64') +* def encodedAuth = Base64.encoder.encodeToString(authStr.bytes) +* def encodedScopes = java.net.URLDecoder.decode(scopes, 'UTF-8') + + +Scenario: Get Token +Given url mainUrl +And print 'mainUrl = '+mainUrl +And print 'grantType = '+grantType +And print 'clientId = '+clientId +And print 'clientSecret = '+clientSecret +And print 'scopes = '+scopes +And print 'authStr = '+authStr +And print 'encodedAuth = '+encodedAuth +And print 'encodedScopes = '+encodedScopes +And header Accept = 'application/json' +And header Authorization = 'Basic '+encodedAuth +And form field grant_type = grantType +And form field scope = scopes +When method POST +Then status 200 +And print 'token response = '+response + + + + +#Scenario: Get Token +#Given url 'https://pujavs.jans.server/jans-auth/restv1/token' +#And header Accept = 'application/json' +#And header Authorization = 'Basic MTgwMi45ZGNkOThhZC1mZTJjLTRmZDktYjcxNy1kOTQzNmQ5ZjIwMDk6dGVzdDEyMzQ=' +#And form field grant_type = 'client_credentials' +#And form field scope = 'https://jans.io/oauth/config/openid/clients.readonly' +#When method POST +#Then status 200 +#And print 'token response = '+response diff --git a/jans-config-api/plugins/scim-plugin/pom.xml b/jans-config-api/plugins/scim-plugin/pom.xml index 6c93d88af0c..10455bd6620 100644 --- a/jans-config-api/plugins/scim-plugin/pom.xml +++ b/jans-config-api/plugins/scim-plugin/pom.xml @@ -6,7 +6,6 @@ 1.0.19-SNAPSHOT 4.0.0 - io.jans.jans-config-api.plugins scim-plugin 4.4.14 diff --git a/jans-config-api/pom.xml b/jans-config-api/pom.xml index 8dd844797d8..1710398bc17 100644 --- a/jans-config-api/pom.xml +++ b/jans-config-api/pom.xml @@ -27,6 +27,7 @@ ${project.version} + 6.0.3.Final 2.13.2 11.0.15 4.0.3.Final @@ -55,6 +56,8 @@ 2.2.7 2.2.7 + + 0.8.3 @@ -382,11 +385,6 @@ - org.apache.commons commons-collections4 @@ -397,6 +395,23 @@ json 20230227 + + + + org.apache.james + apache-mime4j-dom + ${apache.james.version} + + + org.apache.james + apache-mime4j-storage + ${apache.james.version} + + + org.apache.james + apache-mime4j-core + ${apache.james.version} + diff --git a/jans-config-api/profiles/default/config-api-test.properties b/jans-config-api/profiles/default/config-api-test.properties index a7ef3b7a3f7..34395f63bd3 100644 --- a/jans-config-api/profiles/default/config-api-test.properties +++ b/jans-config-api/profiles/default/config-api-test.properties @@ -1,7 +1,7 @@ # The URL of your Jans installation test.server=https://jenkins-config-api.gluu.org -test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/jwks.delete https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete https://jans.io/oauth/jans-auth-server/session.readonly https://jans.io/oauth/jans-auth-server/session.delete revoke_session https://jans.io/oauth/config/read-all https://jans.io/oauth/config/write-all https://jans.io/oauth/config/delete-all https://jans.io/oauth/config/openid-read https://jans.io/oauth/config/openid-write https://jans.io/oauth/config/openid-delete https://jans.io/oauth/config/uma-read https://jans.io/oauth/config/uma-write https://jans.io/oauth/config/uma-delete https://jans.io/oauth/jans-auth-server/config/adminui/user/role.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write https://jans.io/oauth/jans-auth-server/config/adminui/read-all https://jans.io/oauth/jans-auth-server/config/adminui/write-all https://jans.io/oauth/jans-auth-server/config/adminui/user/role.delete https://jans.io/oauth/jans-auth-server/config/adminui/delete-all https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.delete https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.delete https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly https://jans.io/oauth/jans-auth-server/config/adminui/license.write https://jans.io/oauth/config/plugin.readonly https://jans.io/oauth/client/authorizations.readonly https://jans.io/oauth/client/authorizations.delete +test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/jwks.delete https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete https://jans.io/oauth/jans-auth-server/session.readonly https://jans.io/oauth/jans-auth-server/session.delete revoke_session https://jans.io/oauth/config/read-all https://jans.io/oauth/config/write-all https://jans.io/oauth/config/delete-all https://jans.io/oauth/config/openid-read https://jans.io/oauth/config/openid-write https://jans.io/oauth/config/openid-delete https://jans.io/oauth/config/uma-read https://jans.io/oauth/config/uma-write https://jans.io/oauth/config/uma-delete https://jans.io/oauth/jans-auth-server/config/adminui/user/role.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write https://jans.io/oauth/jans-auth-server/config/adminui/read-all https://jans.io/oauth/jans-auth-server/config/adminui/write-all https://jans.io/oauth/jans-auth-server/config/adminui/user/role.delete https://jans.io/oauth/jans-auth-server/config/adminui/delete-all https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.delete https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.delete https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly https://jans.io/oauth/jans-auth-server/config/adminui/license.write https://jans.io/oauth/config/plugin.readonly https://jans.io/oauth/client/authorizations.readonly https://jans.io/oauth/client/authorizations.delete https://jans.io/oauth/config/cacherefresh.readonly https://jans.io/oauth/config/cacherefresh.write https://jans.io/oauth/config/saml.readonly https://jans.io/oauth/config/saml.write https://jans.io/oauth/config/saml-config.readonly https://jans.io/oauth/config/saml-config.write https://jans.io/oauth/config/saml-client-scope.readonly https://jans.io/oauth/config/saml-client-scope.write token.endpoint=https://jenkins-config-api.gluu.org/jans-auth/restv1/token token.grant.type=client_credentials diff --git a/jans-config-api/profiles/local/test.properties b/jans-config-api/profiles/local/test.properties index 09de02959c1..120a0f773b0 100644 --- a/jans-config-api/profiles/local/test.properties +++ b/jans-config-api/profiles/local/test.properties @@ -1,5 +1,5 @@ #LOCAL -test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/jwks.delete https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete https://jans.io/oauth/jans-auth-server/session.readonly https://jans.io/oauth/jans-auth-server/session.delete revoke_session https://jans.io/oauth/config/read-all https://jans.io/oauth/config/write-all https://jans.io/oauth/config/delete-all https://jans.io/oauth/config/openid-read https://jans.io/oauth/config/openid-write https://jans.io/oauth/config/openid-delete https://jans.io/oauth/config/uma-read https://jans.io/oauth/config/uma-write https://jans.io/oauth/config/uma-delete https://jans.io/oauth/jans-auth-server/config/adminui/user/role.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write https://jans.io/oauth/jans-auth-server/config/adminui/read-all https://jans.io/oauth/jans-auth-server/config/adminui/write-all https://jans.io/oauth/jans-auth-server/config/adminui/user/role.delete https://jans.io/oauth/jans-auth-server/config/adminui/delete-all https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.delete https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.delete https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly https://jans.io/oauth/jans-auth-server/config/adminui/license.write https://jans.io/oauth/config/plugin.readonly https://jans.io/oauth/client/authorizations.readonly https://jans.io/oauth/client/authorizations.delete https://jans.io/oauth/config/jans-link.readonly https://jans.io/oauth/config/jans-link.write +test.scopes=https://jans.io/oauth/config/acrs.readonly https://jans.io/oauth/config/acrs.write https://jans.io/oauth/config/attributes.readonly https://jans.io/oauth/config/attributes.write https://jans.io/oauth/config/attributes.delete https://jans.io/oauth/config/cache.readonly https://jans.io/oauth/config/cache.write https://jans.io/oauth/config/openid/clients.readonly https://jans.io/oauth/config/openid/clients.write https://jans.io/oauth/config/openid/clients.delete https://jans.io/oauth/jans-auth-server/config/properties.readonly https://jans.io/oauth/jans-auth-server/config/properties.write https://jans.io/oauth/config/smtp.readonly https://jans.io/oauth/config/smtp.write https://jans.io/oauth/config/smtp.delete https://jans.io/oauth/config/scripts.readonly https://jans.io/oauth/config/scripts.write https://jans.io/oauth/config/scripts.delete https://jans.io/oauth/config/fido2.readonly https://jans.io/oauth/config/fido2.write https://jans.io/oauth/config/jwks.readonly https://jans.io/oauth/config/jwks.write https://jans.io/oauth/config/jwks.delete https://jans.io/oauth/config/database/ldap.readonly https://jans.io/oauth/config/database/ldap.write https://jans.io/oauth/config/database/ldap.delete https://jans.io/oauth/config/logging.readonly https://jans.io/oauth/config/logging.write https://jans.io/oauth/config/scopes.readonly https://jans.io/oauth/config/scopes.write https://jans.io/oauth/config/scopes.delete https://jans.io/oauth/config/uma/resources.readonly https://jans.io/oauth/config/uma/resources.write https://jans.io/oauth/config/uma/resources.delete https://jans.io/oauth/config/database/sql.readonly https://jans.io/oauth/config/database/sql.write https://jans.io/oauth/config/database/sql.delete https://jans.io/oauth/config/stats.readonly jans_stat https://jans.io/scim/users.read https://jans.io/scim/users.write https://jans.io/oauth/config/scim/users.read https://jans.io/oauth/config/scim/users.write https://jans.io/scim/config.readonly https://jans.io/scim/config.write https://jans.io/oauth/config/organization.readonly https://jans.io/oauth/config/organization.write https://jans.io/oauth/config/user.readonly https://jans.io/oauth/config/user.write https://jans.io/oauth/config/user.delete https://jans.io/oauth/config/agama.readonly https://jans.io/oauth/config/agama.write https://jans.io/oauth/config/agama.delete https://jans.io/oauth/jans-auth-server/session.readonly https://jans.io/oauth/jans-auth-server/session.delete revoke_session https://jans.io/oauth/config/read-all https://jans.io/oauth/config/write-all https://jans.io/oauth/config/delete-all https://jans.io/oauth/config/openid-read https://jans.io/oauth/config/openid-write https://jans.io/oauth/config/openid-delete https://jans.io/oauth/config/uma-read https://jans.io/oauth/config/uma-write https://jans.io/oauth/config/uma-delete https://jans.io/oauth/jans-auth-server/config/adminui/user/role.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/role.write https://jans.io/oauth/jans-auth-server/config/adminui/read-all https://jans.io/oauth/jans-auth-server/config/adminui/write-all https://jans.io/oauth/jans-auth-server/config/adminui/user/role.delete https://jans.io/oauth/jans-auth-server/config/adminui/delete-all https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.write https://jans.io/oauth/jans-auth-server/config/adminui/user/permission.delete https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.readonly https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.write https://jans.io/oauth/jans-auth-server/config/adminui/user/rolePermissionMapping.delete https://jans.io/oauth/jans-auth-server/config/adminui/license.readonly https://jans.io/oauth/jans-auth-server/config/adminui/license.write https://jans.io/oauth/config/plugin.readonly https://jans.io/oauth/client/authorizations.readonly https://jans.io/oauth/client/authorizations.delete https://jans.io/oauth/config/jans-link.readonly https://jans.io/oauth/config/jans-link.write https://jans.io/oauth/config/saml.readonly https://jans.io/oauth/config/saml.write https://jans.io/oauth/config/saml-config.readonly https://jans.io/oauth/config/saml-config.write # jans.server token.endpoint=https://jans.server3/jans-auth/restv1/token diff --git a/jans-config-api/server-fips/pom.xml b/jans-config-api/server-fips/pom.xml index 6da7773c530..9ba2f201b20 100644 --- a/jans-config-api/server-fips/pom.xml +++ b/jans-config-api/server-fips/pom.xml @@ -116,6 +116,7 @@ pl.project13.maven git-commit-id-plugin + 2.2.6 get-the-git-infos diff --git a/jans-config-api/server/pom.xml b/jans-config-api/server/pom.xml index 4433b1eb67e..aaa9cdddb2a 100644 --- a/jans-config-api/server/pom.xml +++ b/jans-config-api/server/pom.xml @@ -162,6 +162,11 @@ org.jboss.resteasy resteasy-json-p-provider + + org.jboss.resteasy + resteasy-multipart-provider + ${resteasy.version} + @@ -334,13 +339,6 @@ - - org.apache.maven.plugins - maven-javadoc-plugin - - false - - org.apache.maven.plugins maven-install-plugin diff --git a/jans-config-api/server/src/main/resources/config-api-rs-protect.json b/jans-config-api/server/src/main/resources/config-api-rs-protect.json index d11fe1fe21d..976a3e57767 100644 --- a/jans-config-api/server/src/main/resources/config-api-rs-protect.json +++ b/jans-config-api/server/src/main/resources/config-api-rs-protect.json @@ -2274,6 +2274,144 @@ ] } ] + }, + { + "path": "/jans-config-api/saml/trust-relationship", + "conditions": [ + { + "httpMethods": [ + "GET" + ], + "scopes": [ + { + "inum": "1800.01.56", + "name": "https://jans.io/oauth/config/saml.readonly" + } + ], + "groupScopes": [ + { + "inum": "1800.01.57", + "name": "https://jans.io/oauth/config/saml.write" + } + ], + "superScopes": [ + { + "inum": "1800.03.1", + "name": "https://jans.io/oauth/config/read-all" + } + ] + }, + { + "httpMethods": [ + "POST","PUT","DELETE" + ], + "scopes": [ + { + "inum": "1800.01.57", + "name": "https://jans.io/oauth/config/saml.write" + } + ], + "groupScopes": [], + "superScopes": [ + { + "inum": "1800.03.2", + "name": "https://jans.io/oauth/config/write-all" + } + ] + } + ] + }, + { + "path": "/jans-config-api/saml/samlConfig", + "conditions": [ + { + "httpMethods": [ + "GET" + ], + "scopes": [ + { + "inum": "1800.01.58", + "name": "https://jans.io/oauth/config/saml-config.readonly" + } + ], + "groupScopes": [ + { + "inum": "1800.01.59", + "name": "https://jans.io/oauth/config/saml-config.write" + } + ], + "superScopes": [ + { + "inum": "1800.03.1", + "name": "https://jans.io/oauth/config/read-all" + } + ] + }, + { + "httpMethods": [ + "POST","PUT","DELETE" + ], + "scopes": [ + { + "inum": "1800.01.59", + "name": "https://jans.io/oauth/config/saml-config.write" + } + ], + "groupScopes": [], + "superScopes": [ + { + "inum": "1800.03.2", + "name": "https://jans.io/oauth/config/write-all" + } + ] + } + ] + }, + { + "path": "/jans-config-api/saml/scope", + "conditions": [ + { + "httpMethods": [ + "GET" + ], + "scopes": [ + { + "inum": "1800.01.60", + "name": "https://jans.io/oauth/config/saml-scope.readonly" + } + ], + "groupScopes": [ + { + "inum": "1800.01.61", + "name": "https://jans.io/oauth/config/saml-scope.write" + } + ], + "superScopes": [ + { + "inum": "1800.03.1", + "name": "https://jans.io/oauth/config/read-all" + } + ] + }, + { + "httpMethods": [ + "POST","PUT","DELETE" + ], + "scopes": [ + { + "inum": "1800.01.61", + "name": "https://jans.io/oauth/config/saml-scope.write" + } + ], + "groupScopes": [], + "superScopes": [ + { + "inum": "1800.03.2", + "name": "https://jans.io/oauth/config/write-all" + } + ] + } + ] } ] } \ No newline at end of file diff --git a/jans-config-api/server/src/main/resources/example/saml/config/saml-patch.json b/jans-config-api/server/src/main/resources/example/saml/config/saml-patch.json new file mode 100644 index 00000000000..8cd51dc9cca --- /dev/null +++ b/jans-config-api/server/src/main/resources/example/saml/config/saml-patch.json @@ -0,0 +1,18 @@ +[{ + "op": "replace", + "path": "/samlEnabled", + "value": false +}, +{ + "op": "add", + "path": "/idpConfigs/1", + "value": { + "configId": "shibboleth", + "rootDir": "/opt/idp/configs/shibboleth", + "enabled": false, + "metadataTempDir": "/opt/idp/configs/shibboleth/temp_metadata", + "metadataDir": "/opt/idp/configs/shibboleth/metadata", + "metadataFilePattern": "%s-idp-metadata.xml" + } +} +] \ No newline at end of file diff --git a/jans-config-api/server/src/main/resources/example/saml/config/saml-put.json b/jans-config-api/server/src/main/resources/example/saml/config/saml-put.json new file mode 100644 index 00000000000..903a69b99de --- /dev/null +++ b/jans-config-api/server/src/main/resources/example/saml/config/saml-put.json @@ -0,0 +1,19 @@ +{ + "applicationName":"saml", + "samlTrustRelationshipDn":"ou=trustRelationships,o=jans", + "samlEnabled": "true", + "selectedIdp": "keycloak", + "idpRootDir": "/opt/idp/configs/", + "idpMetadataFilePattern":"%s-idp-metadata.xml", + "spMetadataFilePattern":"%s-sp-metadata.xml", + "idpConfigs":[ + { + "configId":"keycloak", + "rootDir":"/opt/idp/configs/keycloak", + "enabled": "true", + "metadataTempDir": "/opt/idp/configs/keycloak/temp_metadata", + "metadataDir":"/opt/idp/configs/keycloak/metadata", + "metadataFilePattern":"%s-idp-metadata.xml" + } + ] +} diff --git a/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-post.json b/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-post.json new file mode 100644 index 00000000000..cff8bb64c88 --- /dev/null +++ b/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-post.json @@ -0,0 +1,23 @@ +{ + "clientId": "test1", + "displayName": "Test Client 1", + "description": "Test Client description 1", + "rootUrl": "https://jans.com", + "adminUrl": null, + "baseUrl": "/realms/master/account/", + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret123", + "registrationAccessToken": null, + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "spMetaDataSourceType": "FILE", + "spMetaDataFN":"sp_test_file", + "releasedAttributes":[ + "inum=C4F7,ou=scopes,o=jans", + "inum=10B2,ou=scopes,o=jans" + ] +} \ No newline at end of file diff --git a/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-put.json b/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-put.json new file mode 100644 index 00000000000..1f85d5a5fa8 --- /dev/null +++ b/jans-config-api/server/src/main/resources/example/saml/trust-relationship/trust-relationship-put.json @@ -0,0 +1,23 @@ +{ + "clientId": "test2", + "displayName": "Test Client 2", + "description": "SAML client", + "rootUrl": "https://jans.com", + "adminUrl": null, + "baseUrl": "/realms/master/account/", + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret123", + "registrationAccessToken": null, + "redirectUris": [ + "/realms/master/account/*" + ], + "webOrigins": [], + "spMetaDataSourceType": "FILE", + "spMetaDataFN":"sp_test_file", + "releasedAttributes":[ + "inum=C4F7,ou=scopes,o=jans", + "inum=10B2,ou=scopes,o=jans" + ] +} \ No newline at end of file diff --git a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java index 5fb479b20ca..6f6d950b34c 100644 --- a/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java +++ b/jans-config-api/shared/src/main/java/io/jans/configapi/core/rest/BaseResource.java @@ -141,7 +141,7 @@ public static void throwInternalServerException(Throwable throwable) { /** * @param attributeName - * @return + * @return Response */ protected static Response getMissingAttributeError(String attributeName) { ApiError error = new ApiError.ErrorBuilder().withCode(MISSING_ATTRIBUTE_CODE) diff --git a/jans-core/saml/src/pom.xml b/jans-core/saml/src/pom.xml new file mode 100644 index 00000000000..c97ec2d8bc3 --- /dev/null +++ b/jans-core/saml/src/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + jans-core-saml + jans-core-saml + jar + + + io.jans + jans-core-parent + 1.0.16-SNAPSHOT + + + + + + src/main/resources + true + + **/*.xml + **/services/* + **/*.properties + + + + + + src/test/resources + true + + **/*.xml + **/services/* + **/*.properties + + + + + + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + + + io.jans + jans-core-util + + + io.jans + jans-core-document-store + ${project.version} + + + + commons-beanutils + commons-beanutils + + + org.apache.ws.security + wss4j + + + org.opensaml + opensaml + + + ca.juliusdavies + not-yet-commons-ssl + + + + + org.apache.santuario + xmlsec + + + \ No newline at end of file diff --git a/jans-keycloak/.gitignore b/jans-keycloak/.gitignore new file mode 100644 index 00000000000..c53194b720c --- /dev/null +++ b/jans-keycloak/.gitignore @@ -0,0 +1,36 @@ +# Eclipse +.project +jans-config-api-access*.log +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties diff --git a/jans-keycloak/pom.xml b/jans-keycloak/pom.xml new file mode 100644 index 00000000000..302176f384f --- /dev/null +++ b/jans-keycloak/pom.xml @@ -0,0 +1,426 @@ + + + + 4.0.0 + io.jans + jans-keycloak-parent + pom + 1.0.19-SNAPSHOT + jans-keycloak-parent + + + + github + GitHub Packages + https://maven.pkg.github.com/JanssenProject/jans + + + + + 3.3.9 + 3.11.0 + true + 11 + 11 + 3.10.1 + 3.6.0 + UTF-8 + UTF-8 + + ${project.version} + 21.1.1 + 6.0.0 + 3.0.0 + 2.19.0 + 1.7.36 + 2.6 + + 3.0.0-M5 + 3.0.0-RC1 + 5.9.2 + + 1.19.0 + JanssenProject_jans-keycloak + ${project.groupId}:${project.artifactId} + janssenproject + https://sonarcloud.io + + 2.2.7 + 2.2.7 + 2.2.7 + + + + ${maven.min-version} + + + + storage-api + + + + + mavencentral + maven central + https://repo1.maven.org/maven2 + + + jans + Janssen project repository + https://maven.jans.io/maven + + + + + https://github.com/JanssenProject/jans-keycloak + scm:git:git://github.com/JanssenProject/jans-keycloak.git + scm:git:git@github.com:v/jans-keycloak.git + + + + + + + io.jans + jans-bom + ${jans.version} + import + pom + + + + + io.jans + jans-scim-model + ${jans.version} + + + + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet.api.version} + + + jakarta.ws.rs + jakarta.ws.rs-api + ${jakarta.ws.rs.api.version} + + + + + org.keycloak + keycloak-core + ${keycloak.version} + + + org.keycloak + keycloak-server-spi + ${keycloak.version} + + + org.keycloak + keycloak-model-legacy + ${keycloak.version} + + + + + io.smallrye.config + smallrye-config + ${smallrye.config.version} + + + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + + + commons-lang + commons-lang + ${commons.lang.version} + + + org.apache.commons + commons-collections4 + 4.1 + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + + + org.junit.jupiter + junit-jupiter-params + ${junit.jupiter.version} + + + org.testcontainers + junit-jupiter + ${testcontainers.jupiter.version} + + + + + io.swagger.core.v3 + swagger-core-jakarta + ${swagger.version} + + + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + true + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-dependency-plugin + 3.5.0 + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + UTF-8 + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M7 + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire-plugin.version} + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.4 + + + org.apache.maven.plugins + maven-war-plugin + 3.3.2 + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + prepare-agent-ut + + prepare-agent + + + + ${project.build.directory}/jacoco-ut.exec + + + + prepare-agent-it + pre-integration-test + + prepare-agent + + + + ${project.build.directory}/jacoco-it.exec + + + + jacoco-check + + check + + + + + PACKAGE + + + LINE + COVEREDRATIO + 0.50 + + + + + + + + report-ut + + report + + + + ${project.build.directory}/jacoco-ut.exec + + ${project.reporting.outputDirectory}/jacoco-ut + + + + + report-it + post-integration-test + + report + + + + ${project.build.directory}/jacoco-it.exec + + ${project.reporting.outputDirectory}/jacoco-it + + + + + + + + + io.swagger.core.v3 + swagger-maven-plugin-jakarta + ${swagger-maven-plugin-jakarta} + + + + YAML + ${project.parent.basedir}/docs + true + + io.jans.configapi.filters.SpecFilter + + compile + + resolve + + + + + + io.swagger.core.v3 + swagger-models-jakarta + ${swagger-models-jakarta} + + + + + org.codehaus.mojo + buildnumber-maven-plugin + 3.0.0 + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + + + + + + set-configuration-name + + + !cfg + + + + default + + + + + set-skip-test-variable + + + !maven.test.skip + + + + false + + + + \ No newline at end of file diff --git a/jans-keycloak/storage-api/.gitignore b/jans-keycloak/storage-api/.gitignore new file mode 100644 index 00000000000..c53194b720c --- /dev/null +++ b/jans-keycloak/storage-api/.gitignore @@ -0,0 +1,36 @@ +# Eclipse +.project +jans-config-api-access*.log +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties diff --git a/jans-keycloak/storage-api/pom.xml b/jans-keycloak/storage-api/pom.xml new file mode 100644 index 00000000000..d08ac38c312 --- /dev/null +++ b/jans-keycloak/storage-api/pom.xml @@ -0,0 +1,200 @@ + + + + 4.0.0 + jans-keycloak-storage-api + jans-keycloak-storage-api + jar + + + io.jans + jans-keycloak-parent + 1.0.19-SNAPSHOT + + + + ${maven.min-version} + + + + 1.8 + + + + + + + + io.jans + jans-scim-model + ${jans.version} + + + + + jakarta.ws.rs + jakarta.ws.rs-api + + + jakarta.servlet + jakarta.servlet-api + + + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-server-spi + + + org.keycloak + keycloak-model-legacy + + + + + + io.smallrye.config + smallrye-config + + + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-core + + + org.slf4j + slf4j-log4j12 + + + + + commons-collections + commons-collections + + + commons-lang + commons-lang + + + + + org.apache.commons + commons-collections4 + + + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-base + 2.12.6 + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + 2.12.6 + + + + + + io.swagger.core.v3 + swagger-core-jakarta + + + + + + jans-keycloak-storage-api + + + + src/main/resources + true + + **/*.xml + **/*.properties + **/*.json + META-INF/services/*.* + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + ${maven-deps-plugin.version} + + + copy + package + + copy-dependencies + + + ${project.build.directory}/deps + runtime + false + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler.version} + + ${maven-compiler.release} + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + libs/ + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + false + + src/main/assembly/deps-zip.xml + + + + + + + + + + diff --git a/jans-keycloak/storage-api/src/main/assembly/deps-zip.xml b/jans-keycloak/storage-api/src/main/assembly/deps-zip.xml new file mode 100644 index 00000000000..31321b50e74 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/assembly/deps-zip.xml @@ -0,0 +1,26 @@ + + distribution + + zip + + false + + + + io.jans:jans-scim-model + commons-collections:commons-collections + commons-lang:commons-lang + jakarta.ws.rs:jakarta.ws.rs-api + com.fasterxml.jackson.jaxrs:jackson-jaxrs-base + com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider + org.json:json + org.apache.commons:commons-collections4 + org.apache.logging.log4j:log4j-api + org.apache.logging.log4j:log4j-core + + runtime + + + \ No newline at end of file diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/config/JansConfigSource.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/config/JansConfigSource.java new file mode 100644 index 00000000000..3d180dac9f0 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/config/JansConfigSource.java @@ -0,0 +1,132 @@ +package io.jans.idp.keycloak.config; + +import io.jans.idp.keycloak.util.Constants; + +import java.io.FileInputStream; +import java.nio.file.FileSystems; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.keycloak.component.ComponentValidationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JansConfigSource implements ConfigSource { + + private static Logger logger = LoggerFactory.getLogger(JansConfigSource.class); + private static final String CONFIG_FILE_NAME = "jans-keycloak-storage-api.properties"; + + private String configFilePath = null; + private Properties properties = null; + private Map propertiesMap = new HashMap<>(); + + public JansConfigSource() { + this.configFilePath = System.getProperty(Constants.JANS_CONFIG_PROP_PATH); + logger.info("this.configFilePath:{}", configFilePath); + + if (StringUtils.isBlank(configFilePath)) { + throw new ComponentValidationException( + "Configuration property file path `System property` not set, please verify."); + } + + this.loadProperties(); + } + + @Override + public Map getProperties() { + logger.info("\n\n Getting properties \n\n"); + return propertiesMap; + } + + @Override + public Set getPropertyNames() { + logger.debug("\n\n Getting Property Names \n\n"); + try { + return properties.stringPropertyNames(); + + } catch (Exception e) { + logger.error("Unable to read properties from CONFIG_FILE_NAME:{} - error is :{}", CONFIG_FILE_NAME, e); + } + return Collections.emptySet(); + } + + @Override + public int getOrdinal() { + return 800; + } + + @Override + public String getValue(String name) { + try { + logger.trace("JansConfigSource()::getValue() - name:{} - :{}", name, properties.getProperty(name)); + return properties.getProperty(name); + } catch (Exception e) { + logger.error("Unable to read properties from file:{} - error is :{} ", CONFIG_FILE_NAME, e); + } + + return null; + } + + @Override + public String getName() { + return CONFIG_FILE_NAME; + } + + public String getQualifiedFileName() { + String fileSeparator = FileSystems.getDefault().getSeparator(); + logger.info("\n\n JansConfigSource()::getQualifiedFileName() - fileSeparator:{}", fileSeparator); + return this.configFilePath + fileSeparator + CONFIG_FILE_NAME; + } + + private Properties loadProperties() { + logger.info("\n\n\n ***** JansConfigSource::loadProperties() - Properties form Config.Scope "); + + // Get file path + String filePath = getQualifiedFileName(); + logger.info("\n\n\n ***** JansConfigSource::loadProperties() - properties:{}, filePath:{}", properties, filePath); + + if (StringUtils.isBlank(filePath)) { + logger.error("Property filePath is null!"); + throw new ComponentValidationException("Config property filePath is null!!!"); + } + + // load the file handle for main.properties + try (FileInputStream file = new FileInputStream(filePath)) { + logger.info(" JansConfigSource::loadProperties() - file:{}", file); + + // load all the properties from this file + properties = new Properties(); + properties.load(file); + properties.stringPropertyNames().stream() + .forEach(key -> propertiesMap.put(key, properties.getProperty(key))); + + logger.debug("JansConfigSource()::loadProperties() - properties :{}", properties); + + if (properties.isEmpty()) { + logger.error("Could not load config properties!"); + throw new ComponentValidationException("Could not load config properties!!!"); + } + + printProperties(properties); + + } catch (Exception ex) { + logger.error("Failed to load property file", ex); + throw new ComponentValidationException("Failed to load property file!!!"); + } + + return properties; + } + + private static void printProperties(Properties prop) { + if (prop == null || prop.isEmpty()) { + return; + } + prop.keySet().stream().map(key -> key + ": " + prop.getProperty(key.toString())).forEach(logger::debug); + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/exception/JansConfigurationException.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/exception/JansConfigurationException.java new file mode 100644 index 00000000000..f7b45366821 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/exception/JansConfigurationException.java @@ -0,0 +1,27 @@ +/* + * Janssen Project software is available under the Apache License (2004). See http://www.apache.org/licenses/ for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.idp.keycloak.exception; + +public class JansConfigurationException extends RuntimeException { + + + public JansConfigurationException() { + } + + public JansConfigurationException(String message) { + super(message); + } + + public JansConfigurationException(Throwable cause) { + super(cause); + } + + public JansConfigurationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/CredentialAuthenticatingService.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/CredentialAuthenticatingService.java new file mode 100644 index 00000000000..02ca05773d0 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/CredentialAuthenticatingService.java @@ -0,0 +1,38 @@ +package io.jans.idp.keycloak.service; + +import io.jans.idp.keycloak.util.Constants; +import io.jans.idp.keycloak.util.JansUtil; + +import jakarta.ws.rs.core.MediaType; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CredentialAuthenticatingService { + + private static Logger logger = LoggerFactory.getLogger(CredentialAuthenticatingService.class); + private static JansUtil jansUtil = new JansUtil(); + + public boolean authenticateUser(final String username, final String password) { + logger.info("CredentialAuthenticatingService::authenticateUser() - username:{}, password:{} ", username, + password); + boolean isValid = false; + try { + + String token = jansUtil.requestUserToken(jansUtil.getTokenEndpoint(), username, password, null, + Constants.RESOURCE_OWNER_PASSWORD_CREDENTIALS, null, MediaType.APPLICATION_FORM_URLENCODED); + + logger.info("CredentialAuthenticatingService::authenticateUser() - Final token token - {}", token); + + if (StringUtils.isNotBlank(token)) { + isValid = true; + } + } catch (Exception ex) { + ex.printStackTrace(); + logger.error("CredentialAuthenticatingService::authenticateUser() - Error while authenticating is ", ex); + } + return isValid; + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProvider.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProvider.java new file mode 100644 index 00000000000..7cc72863939 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProvider.java @@ -0,0 +1,179 @@ +package io.jans.idp.keycloak.service; + +import io.jans.scim.model.scim2.user.UserResource; + +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputValidator; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.credential.PasswordCredentialModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserLookupProvider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RemoteUserStorageProvider implements CredentialInputValidator, UserLookupProvider, UserStorageProvider { + + private static Logger logger = LoggerFactory.getLogger(RemoteUserStorageProvider.class); + + private KeycloakSession session; + private ComponentModel model; + private UsersApiLegacyService usersService; + private CredentialAuthenticatingService credentialAuthenticatingService = new CredentialAuthenticatingService(); + + public RemoteUserStorageProvider(KeycloakSession session, ComponentModel model) { + logger.info("RemoteUserStorageProvider() - session:{}, model:{}", session, model); + + this.session = session; + this.model = model; + this.usersService = new UsersApiLegacyService(session, model); + } + + @Override + public boolean supportsCredentialType(String credentialType) { + logger.info("RemoteUserStorageProvider::supportsCredentialType() - credentialType:{}", credentialType); + return PasswordCredentialModel.TYPE.equals(credentialType); + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + logger.info("RemoteUserStorageProvider::isConfiguredFor() - realm:{}, user:{}, credentialType:{} ", realm, user, + credentialType); + return user.credentialManager().isConfiguredFor(credentialType); + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput credentialInput) { + logger.info( + "RemoteUserStorageProvider::isValid() - realm:{}, user:{}, credentialInput:{}, user.getUsername():{}, credentialInput.getChallengeResponse():{}", + realm, user, credentialInput, user.getUsername(), credentialInput.getChallengeResponse()); + + boolean valid = credentialAuthenticatingService.authenticateUser(user.getUsername(), + credentialInput.getChallengeResponse()); + + logger.info("RemoteUserStorageProvider::isValid() - valid:{}", valid); + + return valid; + + } + + /** + * Get user based on id + */ + public UserModel getUserById(RealmModel paramRealmModel, String id) { + logger.info("RemoteUserStorageProvider::getUserById() - paramRealmModel:{}, id:{}", paramRealmModel, id); + + UserModel userModel = null; + try { + UserResource user = usersService.getUserById(StorageId.externalId(id)); + logger.info("RemoteUserStorageProvider::getUserById() - user fetched based on id:{} is user:{}", id, user); + if (user != null) { + userModel = createUserModel(paramRealmModel, user); + logger.info(" RemoteUserStorageProvider::getUserById() - userModel:{}", userModel); + + if (userModel != null) { + logger.info( + "RemoteUserStorageProvider::getUserById() - Final userModel fetched with id:{}, userModel:{}, userModel.getAttributes(:{})", + id, userModel, userModel.getAttributes()); + } + } + + logger.info( + "RemoteUserStorageProvider::getUserById() - User fetched with id:{} from external service is:{}", + id, user); + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + "RemoteUserStorageProvider::getUserById() - Error fetching user id:{} from external service is:{} - {} ", + id, ex.getMessage(), ex); + + } + + return userModel; + } + + /** + * Get user based on name + */ + public UserModel getUserByUsername(RealmModel paramRealmModel, String name) { + logger.info("RemoteUserStorageProvider::getUserByUsername() - paramRealmModel:{}, name:{}", paramRealmModel, + name); + + UserModel userModel = null; + try { + UserResource user = usersService.getUserByName(name); + logger.info( + "RemoteUserStorageProvider::getUserByUsername() - User fetched with name:{} from external service is:{}", + name, user); + + if (user != null) { + userModel = createUserModel(paramRealmModel, user); + logger.info("RemoteUserStorageProvider::getUserByUsername() - userModel:{}", userModel); + } + if (userModel != null) { + logger.info( + "RemoteUserStorageProvider::getUserByUsername() - Final User fetched with name:{}, userModel:{}, userModel.getAttributes():{}", + name, userModel, userModel.getAttributes()); + } + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + "\n RemoteUserStorageProvider::getUserByUsername() - Error fetching user name:{}, from external service is:{} - {} ", + name, ex.getMessage(), ex); + + } + return userModel; + } + + public UserModel getUserByEmail(RealmModel paramRealmModel, String email) { + logger.info("RemoteUserStorageProvider::getUserByEmail() - paramRealmModel:{}, email:{}", paramRealmModel, + email); + + UserModel userModel = null; + try { + UserResource user = usersService.getUserByEmail(email); + logger.info( + "RemoteUserStorageProvider::getUserByEmail() - User fetched with email:{} from external service is:{}", + email, user); + + if (user != null) { + userModel = createUserModel(paramRealmModel, user); + logger.info("RemoteUserStorageProvider::getUserByEmail() - userModel:{}", userModel); + } + + if (userModel != null) { + logger.info( + "RemoteUserStorageProvider::getUserByEmail() - Final User fetched with email:{}, userModel:{}, userModel.getAttributes(:{})", + email, userModel, userModel.getAttributes()); + } + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + "\n RemoteUserStorageProvider::getUserByEmail() - Error fetching user email:{}, from external service is:{} - {} ", + email, ex.getMessage(), ex); + + } + return userModel; + } + + public void close() { + logger.info("RemoteUserStorageProvider::close()"); + + } + + private UserModel createUserModel(RealmModel realm, UserResource user) { + logger.info("RemoteUserStorageProvider::createUserModel() - realm:{} , user:{}", realm, user); + + UserModel userModel = new UserAdapter(session, realm, model, user); + logger.info("Final RemoteUserStorageProvider::createUserModel() - userModel:{}", userModel); + + return userModel; + } +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProviderFactory.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProviderFactory.java new file mode 100644 index 00000000000..a90971b3cc4 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/RemoteUserStorageProviderFactory.java @@ -0,0 +1,55 @@ +package io.jans.idp.keycloak.service; + +import io.jans.idp.keycloak.util.Constants; + +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.UserStorageProviderFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RemoteUserStorageProviderFactory implements UserStorageProviderFactory { + + private static Logger logger = LoggerFactory.getLogger(RemoteUserStorageProviderFactory.class); + public static final String PROVIDER_NAME = "jans-keycloak-storage-api"; + + @Override + public RemoteUserStorageProvider create(KeycloakSession session, ComponentModel model) { + logger.info("RemoteUserStorageProviderFactory::create() - session:{}, model:{}", session, model); + return new RemoteUserStorageProvider(session, model); + } + + @Override + public String getId() { + String id = PROVIDER_NAME; + logger.info("id:{}", id); + + return id; + } + + @Override + public String getHelpText() { + return "Jans Remote User Provider"; + } + + @Override + public void init(Config.Scope config) { + logger.info( + "RemoteUserStorageProviderFactory::init() - config:{}, System.getProperty(Constants.JANS_CONFIG_PROP_PATH):{}", + config, System.getProperty(Constants.JANS_CONFIG_PROP_PATH)); + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + logger.info("RemoteUserStorageProviderFactory::postInit() - config:{}", factory); + } + + @Override + public void close() { + logger.info("RemoteUserStorageProviderFactory::close() - Exit"); + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/ScimService.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/ScimService.java new file mode 100644 index 00000000000..f2c9e49e93e --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/ScimService.java @@ -0,0 +1,191 @@ +package io.jans.idp.keycloak.service; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.jans.idp.keycloak.util.JansUtil; +import io.jans.scim.model.scim2.SearchRequest; +import io.jans.scim.model.scim2.user.UserResource; +import jakarta.ws.rs.WebApplicationException; + + +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.keycloak.broker.provider.util.SimpleHttp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.keycloak.util.JsonSerialization; + +public class ScimService { + + private static Logger LOG = LoggerFactory.getLogger(ScimService.class); + private static JansUtil jansUtil = new JansUtil(); + + private String getScimUserEndpoint() { + String scimUserEndpoint = jansUtil.getScimUserEndpoint(); + LOG.info("ScimService::getScimUserEndpoint() - scimUserEndpoint:{}", scimUserEndpoint); + return scimUserEndpoint; + } + + private String getScimUserSearchEndpoint() { + String scimUserSearchEndpoint = jansUtil.getScimUserSearchEndpoint(); + LOG.info("ScimService::getScimUserSearchEndpoint() - scimUserSearchEndpoint:{}", scimUserSearchEndpoint); + return scimUserSearchEndpoint; + } + + private String requestAccessToken() { + LOG.info("ScimService::requestAccessToken()"); + String token = null; + + try { + token = jansUtil.requestScimAccessToken(); + LOG.info("ScimService::requestAccessToken() - token:{}", token); + } catch (Exception ex) { + LOG.error("ScimService::requestAccessToken() - Error while generating access token for SCIM endpoint is:{}", + ex); + throw new WebApplicationException( + "ScimService::requestAccessToken() - Error while generating access token for SCIM endpoint is = " + + ex); + } + return token; + } + + public UserResource getUserById(String inum) { + LOG.info(" ScimService::getUserById() - inum:{}", inum); + try { + return getData(getScimUserEndpoint() + "/" + inum, this.requestAccessToken()); + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error( + "ScimService::getUserById() - Error fetching user based on inum:{} from external service is:{} - {} ", + inum, ex.getMessage(), ex); + } + return null; + } + + public UserResource getUserByName(String username) { + LOG.info("ScimService::getUserByName() - username:{}", username); + try { + + String filter = "userName eq \"" + username + "\""; + return postData(this.getScimUserSearchEndpoint(), this.requestAccessToken(), filter); + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error( + "ScimService::getUserByName() - Error fetching user based on username:{} from external service is:{} - {} ", + username, ex.getMessage(), ex); + } + return null; + } + + public UserResource getUserByEmail(String email) { + LOG.info(" ScimService::getUserByEmail() - email:{}", email); + try { + + String filter = "emails[value eq \"" + email + "\"]"; + LOG.info(" ScimService::getUserByEmail() - filter:{}", filter); + return postData(this.getScimUserSearchEndpoint(), this.requestAccessToken(), filter); + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error( + " ScimService::getUserByEmail() - Error fetching user based on email:{} from external service is:{} - {} ", + email, ex.getMessage(), ex); + + } + return null; + } + + public UserResource postData(String uri, String accessToken, String filter) { + UserResource user = null; + LOG.info("ScimService::postData() - uri:{}, accessToken:{}, filter:{}", uri, accessToken, filter); + try { + HttpClient client = HttpClientBuilder.create().build(); + + SearchRequest searchRequest = createSearchRequest(filter); + LOG.info("ScimService::postData() - client:{}, searchRequest:{}, accessToken:{}", client, searchRequest, + accessToken); + + JsonNode jsonNode = SimpleHttp.doPost(uri, client).auth(accessToken).json(searchRequest).asJson(); + + LOG.info("\n\n ScimService::postData() - jsonNode:{}", jsonNode); + + user = getUserResourceFromList(jsonNode); + + LOG.info("ScimService::postData() - user:{}", user); + + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error("\n\n ScimService::postData() - Error while fetching data is ex:{}", ex); + } + return user; + } + + public UserResource getData(String uri, String accessToken) { + UserResource user = null; + LOG.info("ScimService::getData() - uri:{}, accessToken:{}", uri, accessToken); + try { + HttpClient client = HttpClientBuilder.create().build(); + + JsonNode jsonNode = SimpleHttp.doGet(uri, client).auth(accessToken).asJson(); + + LOG.info("\n\n ScimService::getData() - jsonNode:{}", jsonNode); + + user = getUserResource(jsonNode); + + LOG.info("ScimService::getData() - user:{}", user); + + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error("\n\n ScimService::getData() - Error while fetching data is ex:{}", ex); + } + return user; + } + + private SearchRequest createSearchRequest(String filter) { + LOG.info("ScimService::createSearchRequest() - createSearchRequest() - filter:{}", filter); + SearchRequest searchRequest = new SearchRequest(); + searchRequest.setFilter(filter); + + LOG.info(" ScimService::createSearchRequest() - searchRequest:{}", searchRequest); + + return searchRequest; + } + + private UserResource getUserResourceFromList(JsonNode jsonNode) { + LOG.info(" \n\n ScimService::getUserResourceFromList() - jsonNode:{}", jsonNode); + + UserResource user = null; + try { + if (jsonNode != null) { + if (jsonNode.get("Resources") != null) { + JsonNode value = jsonNode.get("Resources").get(0); + LOG.info("\n\n *** ScimService::getUserResourceFromList() - value:{}, value.getClass():{}", value, + value.getClass()); + user = JsonSerialization.readValue(JsonSerialization.writeValueAsBytes(value), UserResource.class); + LOG.info(" ScimService::getUserResourceFromList() - user:{}, user.getClass():{}", user, + user.getClass()); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error("\n\n ScimService::getUserResourceFromList() - Error while fetching data is ex:{}", ex); + } + return user; + } + + private UserResource getUserResource(JsonNode jsonNode) { + LOG.info(" \n\n ScimService::getUserResource() - jsonNode:{}", jsonNode); + + UserResource user = null; + try { + if (jsonNode != null) { + user = JsonSerialization.readValue(JsonSerialization.writeValueAsBytes(jsonNode), UserResource.class); + LOG.info(" ScimService::getUserResource() - user:{}, user.getClass():{}", user, user.getClass()); + } + } catch (Exception ex) { + ex.printStackTrace(); + LOG.error("\n\n ScimService::getUserResource() - Error while fetching data is ex:{}", ex); + } + return user; + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UserAdapter.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UserAdapter.java new file mode 100644 index 00000000000..4594d8907f8 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UserAdapter.java @@ -0,0 +1,141 @@ +package io.jans.idp.keycloak.service; + +import io.jans.idp.keycloak.util.JansDataUtil; +import io.jans.scim.model.scim2.user.UserResource; +import io.jans.scim.model.scim2.util.DateUtil; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.apache.commons.lang.StringUtils; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.LegacyUserCredentialManager; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.SubjectCredentialManager; +import org.keycloak.models.UserModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.adapter.AbstractUserAdapter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserAdapter extends AbstractUserAdapter { + private static Logger logger = LoggerFactory.getLogger(UserAdapter.class); + private final UserResource user; + + public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserResource user) { + + super(session, realm, model); + logger.debug( + " UserAdapter() - model:{}, user:{}, storageProviderModel:{}, storageProviderModel.getId():{}, user.getId():{}", + model, user, storageProviderModel, storageProviderModel.getId(), user.getId()); + this.storageId = new StorageId(storageProviderModel.getId(), user.getId()); + this.user = user; + + logger.debug("UserAdapter() - All User Resource field():{}", printUserResourceField()); + } + + @Override + public String getUsername() { + return user.getUserName(); + } + + @Override + public String getFirstName() { + return user.getDisplayName(); + } + + @Override + public String getLastName() { + return user.getNickName(); + } + + @Override + public String getEmail() { + return ((user.getEmails() != null && user.getEmails().get(0) != null) ? user.getEmails().get(0).getValue() + : null); + } + + @Override + public SubjectCredentialManager credentialManager() { + return new LegacyUserCredentialManager(session, realm, this); + } + + public Map getCustomAttributes() { + printUserCustomAttributes(); + return user.getCustomAttributes(); + } + + @Override + public boolean isEnabled() { + boolean enabled = false; + if (user != null) { + enabled = user.getActive(); + } + return enabled; + } + + @Override + public Long getCreatedTimestamp() { + Long createdDate = null; + if (user.getMeta().getCreated() != null) { + String created = user.getMeta().getCreated(); + if (created != null && StringUtils.isNotBlank(created)) { + createdDate = DateUtil.ISOToMillis(created); + } + } + return createdDate; + } + + @Override + public Map> getAttributes() { + MultivaluedHashMap attributes = new MultivaluedHashMap<>(); + attributes.add(UserModel.USERNAME, getUsername()); + attributes.add(UserModel.EMAIL, getEmail()); + attributes.add(UserModel.FIRST_NAME, getFirstName()); + attributes.add(UserModel.LAST_NAME, getLastName()); + return attributes; + } + + @Override + public Stream getAttributeStream(String name) { + if (name.equals(UserModel.USERNAME)) { + return Stream.of(getUsername()); + } + return Stream.empty(); + } + + @Override + protected Set getRoleMappingsInternal() { + return Set.of(); + } + + private void printUserCustomAttributes() { + logger.info(" UserAdapter::printUserCustomAttributes() - user:{}", user); + if (user == null || user.getCustomAttributes() == null || user.getCustomAttributes().isEmpty()) { + return; + } + + user.getCustomAttributes().keySet().stream() + .forEach(key -> logger.info("key:{} , value:{}", key, user.getCustomAttributes().get(key))); + + } + + private Map printUserResourceField() { + logger.info(" UserAdapter::printUserResourceField() - user:{}", user); + Map propertyTypeMap = null; + if (user == null) { + return propertyTypeMap; + } + + propertyTypeMap = JansDataUtil.getFieldTypeMap(user.getClass()); + logger.info("UserAdapter::printUserResourceField() - all fields of user:{}", propertyTypeMap); + return propertyTypeMap; + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UsersApiLegacyService.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UsersApiLegacyService.java new file mode 100644 index 00000000000..b028013be5b --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/service/UsersApiLegacyService.java @@ -0,0 +1,72 @@ +package io.jans.idp.keycloak.service; + +import io.jans.scim.model.scim2.user.UserResource; + +import java.util.Properties; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UsersApiLegacyService { + + private static Logger logger = LoggerFactory.getLogger(UsersApiLegacyService.class); + private ScimService scimService = new ScimService(); + + private KeycloakSession session; + private ComponentModel model; + protected Properties jansProperties = new Properties(); + + public UsersApiLegacyService(KeycloakSession session, ComponentModel model) { + logger.info(" UsersApiLegacyService() - session:{}, model:{}", session, model); + + this.session = session; + this.model = model; + } + + public UserResource getUserById(String inum) { + logger.info("UsersApiLegacyService::getUserById() - inum:{}", inum); + try { + return scimService.getUserById(inum); + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + "UsersApiLegacyService::getUserById() - Error fetching user based on inum:{} from external service is:{} - {} ", + inum, ex.getMessage(), ex); + + } + return null; + } + + public UserResource getUserByName(String username) { + logger.info(" UsersApiLegacyService::getUserByName() - username:{}", username); + try { + + return scimService.getUserByName(username); + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + "UsersApiLegacyService::getUserByName() - Error fetching user based on username:{} from external service is:{} - {} ", + username, ex.getMessage(), ex); + + } + return null; + } + + public UserResource getUserByEmail(String email) { + logger.info(" UsersApiLegacyService::getUserByEmail() - email:{}", email); + try { + + return scimService.getUserByEmail(email); + } catch (Exception ex) { + ex.printStackTrace(); + logger.error( + " UsersApiLegacyService::getUserByEmail() - Error fetching user based on email:{} from external service is:{} - {} ", + email, ex.getMessage(), ex); + + } + return null; + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/Constants.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/Constants.java new file mode 100644 index 00000000000..11b23b89f43 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/Constants.java @@ -0,0 +1,33 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.idp.keycloak.util; + +public class Constants { + + private Constants() {} + + public static final String JANS_CONFIG_PROP_PATH = "jans.config.prop.path"; + public static final String KEYCLOAK_USER = "/keycloak-user"; + public static final String BASE_URL = "https://localhost"; + + public static final String SCOPE_TYPE_OPENID = "openid"; + public static final String UTF8_STRING_ENCODING = "UTF-8"; + public static final String CLIENT_SECRET_BASIC = "client_secret_basic"; + public static final String CLIENT_CREDENTIALS = "client_credentials"; + public static final String RESOURCE_OWNER_PASSWORD_CREDENTIALS = "password"; + + //properties + public static final String KEYCLOAK_SERVER_URL = "keycloak.server.url"; + public static final String AUTH_TOKEN_ENDPOINT = "auth.token.endpoint"; + public static final String SCIM_USER_ENDPOINT = "scim.user.endpoint"; + public static final String SCIM_USER_SEARCH_ENDPOINT = "scim.user.search.endpoint"; + public static final String SCIM_OAUTH_SCOPE = "scim.oauth.scope"; + public static final String KEYCLOAK_SCIM_CLIENT_ID = "keycloak.scim.client.id"; + public static final String KEYCLOAK_SCIM_CLIENT_PASSWORD = "keycloak.scim.client.password"; + + +} \ No newline at end of file diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansDataUtil.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansDataUtil.java new file mode 100644 index 00000000000..85a22e3d9b1 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansDataUtil.java @@ -0,0 +1,109 @@ +package io.jans.idp.keycloak.util; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JansDataUtil { + + private static final Logger logger = LoggerFactory.getLogger(JansDataUtil.class); + + public static Object invokeMethod(Class clazz, String methodName, Class... parameterTypes) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + logger.debug("JansDataUtil::invokeMethod() - Invoke clazz:{} on methodName:{} with name:{} ", clazz, methodName, + parameterTypes); + Object obj = null; + if (clazz == null || methodName == null || parameterTypes == null) { + return obj; + } + Method m = clazz.getDeclaredMethod(methodName, parameterTypes); + obj = m.invoke(null, parameterTypes); + + logger.debug("JansDataUtil::invokeMethod() - methodName:{} returned obj:{} ", methodName, obj); + return obj; + } + + public Object invokeReflectionGetter(Object obj, String variableName) { + logger.debug("JansDataUtil::invokeMethod() - Invoke obj:{}, variableName:{}", obj, variableName); + try { + if (obj == null) { + return obj; + } + PropertyDescriptor pd = new PropertyDescriptor(variableName, obj.getClass()); + Method getter = pd.getReadMethod(); + logger.debug("JansDataUtil::invokeMethod() - Invoke getter:{}", getter); + if (getter != null) { + return getter.invoke(obj); + } else { + logger.error( + "JansDataUtil::invokeReflectionGetter() - Getter Method not found for class:{} property:{}", + obj.getClass().getName(), variableName); + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException + | IntrospectionException e) { + logger.error(String.format( + "JansDataUtil::invokeReflectionGetter() - Getter Method ERROR for class: %s property: %s", + obj.getClass().getName(), variableName), e); + } + return obj; + } + + public static List getAllFields(Class type) { + logger.debug("JansDataUtil::getAllFields() - type:{} ", type); + List allFields = new ArrayList<>(); + if (type == null) { + return allFields; + } + getAllFields(allFields, type); + logger.debug("JansDataUtil::getAllFields() - Fields:{} of type:{} ", allFields, type); + return allFields; + } + + public static List getAllFields(List fields, Class type) { + logger.debug("JansDataUtil::getAllFields() - fields:{} , type:{} ", fields, type); + if (fields == null || type == null) { + return fields; + } + fields.addAll(Arrays.asList(type.getDeclaredFields())); + + if (type.getSuperclass() != null) { + getAllFields(fields, type.getSuperclass()); + } + logger.debug("JansDataUtil::getAllFields() - Final fields:{} of type:{} ", fields, type); + return fields; + } + + public static Map getFieldTypeMap(Class clazz) { + logger.debug("JansDataUtil::getFieldTypeMap() - clazz:{} ", clazz); + Map propertyTypeMap = new HashMap<>(); + + if (clazz == null) { + return propertyTypeMap; + } + + List fields = getAllFields(clazz); + logger.debug("JansDataUtil::getFieldTypeMap() - all-fields:{} ", fields); + + for (Field field : fields) { + logger.debug( + "JansDataUtil::getFieldTypeMap() - field:{} , field.getAnnotatedType():{}, field.getAnnotations():{} , field.getType().getAnnotations():{}, field.getType().getCanonicalName():{} , field.getType().getClass():{} , field.getType().getClasses():{} , field.getType().getComponentType():{}", + field, field.getAnnotatedType(), field.getAnnotations(), field.getType().getAnnotations(), + field.getType().getCanonicalName(), field.getType().getClass(), field.getType().getClasses(), + field.getType().getComponentType()); + propertyTypeMap.put(field.getName(), field.getType().getSimpleName()); + } + logger.debug("JansDataUtil::getFieldTypeMap() - Final propertyTypeMap{} ", propertyTypeMap); + return propertyTypeMap; + } + +} diff --git a/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansUtil.java b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansUtil.java new file mode 100644 index 00000000000..3ae58e5204e --- /dev/null +++ b/jans-keycloak/storage-api/src/main/java/io/jans/idp/keycloak/util/JansUtil.java @@ -0,0 +1,270 @@ +package io.jans.idp.keycloak.util; + +import com.fasterxml.jackson.databind.JsonNode; + +import io.jans.idp.keycloak.config.JansConfigSource; + +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.WebApplicationException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.*; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.keycloak.broker.provider.util.SimpleHttp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JansUtil { + private static Logger logger = LoggerFactory.getLogger(JansUtil.class); + private JansConfigSource jansConfigSource = new JansConfigSource(); + private Map configProperties = null; + + public JansUtil() { + logger.debug("\nJ ansUtil() - Getting properties"); + configProperties = jansConfigSource.getProperties(); + if (configProperties == null || configProperties.isEmpty()) { + throw new WebApplicationException("Config properties is null!!!"); + } + } + + public String getTokenEndpoint() { + logger.debug("\n JansUtil::getTokenEndpoint() - configProperties.get(Constants.AUTH_TOKEN_ENDPOINT)():{}", + configProperties.get(Constants.AUTH_TOKEN_ENDPOINT)); + return configProperties.get(Constants.AUTH_TOKEN_ENDPOINT); + } + + public String getScimUserEndpoint() { + logger.debug(" \n JansUtil::getScimUserEndpoint() - configProperties.get(Constants.SCIM_USER_ENDPOINT)():{}", + configProperties.get(Constants.SCIM_USER_ENDPOINT)); + return configProperties.get(Constants.SCIM_USER_ENDPOINT); + } + + public String getScimUserSearchEndpoint() { + logger.debug( + "\n JansUtil::getScimUserSearchEndpoint() - configProperties.get(Constants.SCIM_USER_SEARCH_ENDPOINT)():{}", + configProperties.get(Constants.SCIM_USER_SEARCH_ENDPOINT)); + return configProperties.get(Constants.SCIM_USER_SEARCH_ENDPOINT); + } + + public String getClientId() { + logger.debug(" \n JansUtil::getClientId() - configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_ID)():{}", + configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_ID)); + return configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_ID); + } + + public String getClientPassword() { + logger.debug(" \n JansUtil::getClientPassword() - configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_PASSWORD)():{}", + configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_PASSWORD)); + return configProperties.get(Constants.KEYCLOAK_SCIM_CLIENT_PASSWORD); + } + + public String getScimOauthScope() { + logger.debug(" \n JansUtil::getScimOauthScope() - configProperties.get(Constants.SCIM_OAUTH_SCOPE)():{}", + configProperties.get(Constants.SCIM_OAUTH_SCOPE)); + return configProperties.get(Constants.SCIM_OAUTH_SCOPE); + } + + public String requestScimAccessToken() throws IOException { + logger.info(" \n JansUtil::requestScimAccessToken() "); + List scopes = new ArrayList<>(); + scopes.add(getScimOauthScope()); + String token = requestAccessToken(getClientId(), scopes); + logger.info("JansUtil::requestScimAccessToken() - token:{} ", token); + return token; + } + + public String requestAccessToken(final String clientId, final List scope) throws IOException { + logger.info("JansUtil::requestAccessToken() - Request for AccessToken - clientId:{}, scope:{} ", clientId, + scope); + + String tokenUrl = getTokenEndpoint(); + String token = getAccessToken(tokenUrl, clientId, scope); + logger.info("JansUtil::requestAccessToken() - oAuth AccessToken response - token:{}", token); + + return token; + } + + public String getAccessToken(final String tokenUrl, final String clientId, final List scopes) + throws IOException { + logger.info("JansUtil::getAccessToken() - Access Token Request - tokenUrl:{}, clientId:{}, scopes:{}", tokenUrl, + clientId, scopes); + + // Get clientSecret + String clientSecret = this.getClientPassword(); + logger.info("JansUtil::getAccessToken() - Access Token Request - clientId:{}, clientSecret:{}", clientId, + clientSecret); + + // distinct scopes + Set scopesSet = new HashSet<>(scopes); + StringBuilder scope = new StringBuilder(Constants.SCOPE_TYPE_OPENID); + for (String s : scopesSet) { + scope.append(" ").append(s); + } + + logger.info("JansUtil::getAccessToken() - Scope required - {}", scope); + + String token = requestAccessToken(tokenUrl, clientId, clientSecret, scope.toString(), + Constants.CLIENT_CREDENTIALS, Constants.CLIENT_SECRET_BASIC, MediaType.APPLICATION_FORM_URLENCODED); + logger.info("JansUtil::getAccessToken() - Final token token - {}", token); + return token; + } + + public String requestAccessToken(final String tokenUrl, final String clientId, final String clientSecret, + final String scope, String grantType, String authenticationMethod, String mediaType) throws IOException { + logger.info( + "JansUtil::requestAccessToken() - Request for Access Token - tokenUrl:{}, clientId:{}, clientSecret:{}, scope:{}, grantType:{}, authenticationMethod:{}, mediaType:{}", + tokenUrl, clientId, clientSecret, scope, grantType, authenticationMethod, mediaType); + String token = null; + try { + + logger.info(" JansUtil::requestAccessToken() - this.getEncodedCredentials():{}", + this.getEncodedCredentials(clientId, clientSecret)); + HttpClient client = HttpClientBuilder.create().build(); + JsonNode jsonNode = SimpleHttp.doPost(tokenUrl, client) + .header("Authorization", "Basic " + this.getEncodedCredentials(clientId, clientSecret)) + .header("Content-Type", mediaType).param("grant_type", "client_credentials") + .param("username", clientId + ":" + clientSecret).param("scope", scope).param("client_id", clientId) + .param("client_secret", clientSecret).param("authorization_method", "client_secret_basic").asJson(); + logger.info("\n JansUtil::requestAccessToken() - POST Request for Access Token - jsonNode:{} ", jsonNode); + + token = this.getToken(jsonNode); + + logger.info("\n JansUtil::requestAccessToken() - After Post request for Access Token - token:{} ", token); + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error("\n JansUtil::requestAccessToken() - Post error is ", ex); + } + return token; + } + + public String requestUserToken(final String tokenUrl, final String username, final String password, + final String scope, String grantType, String authenticationMethod, String mediaType) throws IOException { + logger.info( + "JansUtil::requestUserToken() - Request for Access Token - tokenUrl:{}, username:{}, password:{}, scope:{}, grantType:{}, authenticationMethod:{}, mediaType:{}", + tokenUrl, username, password, scope, grantType, authenticationMethod, mediaType); + String token = null; + try { + String clientId = this.getClientId(); + String clientSecret = this.getClientPassword(); + + logger.info( + " JansUtil::requestUserToken() - clientId:{} , clientSecret:{}, this.getEncodedCredentials():{}", + clientId, clientSecret, this.getEncodedCredentials(clientId, clientSecret)); + HttpClient client = HttpClientBuilder.create().build(); + JsonNode jsonNode = SimpleHttp.doPost(tokenUrl, client) + .header("Authorization", "Basic " + this.getEncodedCredentials(clientId, clientSecret)) + .header("Content-Type", mediaType).param("grant_type", grantType).param("username", username) + .param("password", password).asJson(); + + logger.info("\n JansUtil::requestUserToken() - After invoking post request for user token - jsonNode:{} ", + jsonNode); + + token = this.getToken(jsonNode); + + logger.info("\n JansUtil::requestUserToken() -POST Request for Access Token - token:{} ", token); + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error("\n JansUtil::requestUserToken() - Post error is ", ex); + } + return token; + } + + private boolean validateTokenScope(JsonNode jsonNode, String scope) { + + logger.info(" \n\n JansUtil::validateTokenScope() - jsonNode:{}, scope:{}", jsonNode, scope); + boolean validScope = false; + try { + + List scopeList = Stream.of(scope.split(" ", -1)).collect(Collectors.toList()); + + if (jsonNode != null && jsonNode.get("scope") != null) { + JsonNode value = jsonNode.get("scope"); + logger.info("\n\n *** JansUtil::validateTokenScope() - value:{}", value); + + if (value != null) { + String responseScope = value.toString(); + logger.info( + "JansUtil::validateTokenScope() - scope:{}, responseScope:{}, responseScope.contains(scope):{}", + scope, responseScope, responseScope.contains(scope)); + if (scopeList.contains(responseScope)) { + validScope = true; + } + } + + } + logger.info("JansUtil::validateTokenScope() - validScope:{}", validScope); + + } catch (Exception ex) { + ex.printStackTrace(); + logger.error("\n JansUtil::validateTokenScope() - Error while validating token scope from response is ", + ex); + } + return validScope; + + } + + private String getToken(JsonNode jsonNode) { + logger.info(" \n\n JansUtil::getToken() - jsonNode:{}", jsonNode); + + String token = null; + try { + + if (jsonNode != null && jsonNode.get("access_token") != null) { + JsonNode value = jsonNode.get("access_token"); + logger.info("\n\n *** JansUtil::getToken() - value:{}", value); + + if (value != null) { + token = value.asText(); + } + logger.info("getToken() - token:{}", token); + } + } catch (Exception ex) { + ex.printStackTrace(); + logger.error("\n\n Error while getting token from response is ", ex); + } + return token; + } + + private boolean hasCredentials(String authUsername, String authPassword) { + return (StringUtils.isNotBlank(authUsername) && StringUtils.isNotBlank(authPassword)); + } + + /** + * Returns the client credentials (URL encoded). + * + * @return The client credentials. + */ + private String getCredentials(String authUsername, String authPassword) throws UnsupportedEncodingException { + logger.info("getCredentials() - authUsername:{}, authPassword:{}", authUsername, authPassword); + return URLEncoder.encode(authUsername, Constants.UTF8_STRING_ENCODING) + ":" + + URLEncoder.encode(authPassword, Constants.UTF8_STRING_ENCODING); + } + + private String getEncodedCredentials(String authUsername, String authPassword) { + logger.info("getEncodedCredentials() - authUsername:{}, authPassword:{}", authUsername, authPassword); + try { + if (hasCredentials(authUsername, authPassword)) { + return Base64.encodeBase64String(getBytes(getCredentials(authUsername, authPassword))); + } + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return null; + } + + private static byte[] getBytes(String str) { + return str.getBytes(StandardCharsets.UTF_8); + } + +} diff --git a/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 00000000000..fb31e7be233 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +io.jans.idp.keycloak.config.JansConfigSource \ No newline at end of file diff --git a/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 00000000000..af5dd9ee27e --- /dev/null +++ b/jans-keycloak/storage-api/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +io.jans.idp.keycloak.service.RemoteUserStorageProviderFactory \ No newline at end of file diff --git a/jans-keycloak/storage-api/src/main/resources/jans-keycloak-storage-api.properties b/jans-keycloak/storage-api/src/main/resources/jans-keycloak-storage-api.properties new file mode 100644 index 00000000000..292f8cf82fd --- /dev/null +++ b/jans-keycloak/storage-api/src/main/resources/jans-keycloak-storage-api.properties @@ -0,0 +1,9 @@ +# Sample properties +keycloak.server.url=https://${keycloack_hostname} +auth.token.endpoint=https://${hostname}/jans-auth/restv1/token +scim.user.endpoint=https://${hostname}/jans-scim/restv1/v2/Users +scim.user.search.endpoint=https://${hostname}/jans-scim/restv1/v2/Users/.search +scim.oauth.scope=https://jans.io/scim/users.read +keycloak.scim.client.id=${saml_scim_client_id} +keycloak.scim.client.password=${saml_scim_client_pw} + diff --git a/jans-keycloak/storage-api/src/main/resources/log4j2.xml b/jans-keycloak/storage-api/src/main/resources/log4j2.xml new file mode 100644 index 00000000000..d6f099dc2f6 --- /dev/null +++ b/jans-keycloak/storage-api/src/main/resources/log4j2.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jans-keycloak/storage-api/src/test/java/io/jans/idp/keycloak/TestJenkinsRunner.java b/jans-keycloak/storage-api/src/test/java/io/jans/idp/keycloak/TestJenkinsRunner.java new file mode 100644 index 00000000000..cd56722799c --- /dev/null +++ b/jans-keycloak/storage-api/src/test/java/io/jans/idp/keycloak/TestJenkinsRunner.java @@ -0,0 +1,29 @@ +/* + * Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text. + * + * Copyright (c) 2020, Janssen Project + */ + +package io.jans.idp.keycloak; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class TestJenkinsRunner { + + void testParallel() { + + } + + public static void generateReport(String karateOutputPath) { + + } +} diff --git a/jans-keycloak/storage-api/src/test/java/io/jans/jans-keycloak/AppTest.java b/jans-keycloak/storage-api/src/test/java/io/jans/jans-keycloak/AppTest.java new file mode 100644 index 00000000000..a0198b7ac3d --- /dev/null +++ b/jans-keycloak/storage-api/src/test/java/io/jans/jans-keycloak/AppTest.java @@ -0,0 +1,38 @@ +package io.jans.jans-keycloak; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/jans-linux-setup/jans_setup/app_info.json b/jans-linux-setup/jans_setup/app_info.json index fbfc19ea533..c39b55cf0d6 100644 --- a/jans-linux-setup/jans_setup/app_info.json +++ b/jans-linux-setup/jans_setup/app_info.json @@ -14,5 +14,6 @@ "PYGMENTS": "https://github.com/pygments/pygments/archive/refs/tags/2.13.0.zip", "CRYPTOGRAPHY": "https://files.pythonhosted.org/packages/20/8b/66600f5851ec7893ace9b74445d7eaf3499571b347e339d18c76c876b0f9/cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", "TWILIO_MAVEN": "https://repo1.maven.org/maven2/com/twilio/sdk/twilio/", - "TWILIO_VERSION": "7.17.0" + "TWILIO_VERSION": "7.17.0", + "KC_VERSION": "22.0.3" } diff --git a/jans-linux-setup/jans_setup/jans_setup.py b/jans-linux-setup/jans_setup/jans_setup.py index a629f372217..d2535863f91 100755 --- a/jans-linux-setup/jans_setup/jans_setup.py +++ b/jans-linux-setup/jans_setup/jans_setup.py @@ -143,6 +143,8 @@ def ami_packaged(): from setup_app.installers.jans_link import JansLinkInstaller from setup_app.installers.jans_casa import CasaInstaller + from setup_app.installers.jans_saml import JansSamlInstaller + from setup_app.installers.config_api import ConfigApiInstaller from setup_app.installers.jans_cli import JansCliInstaller @@ -263,6 +265,7 @@ def ami_packaged(): elevenInstaller = ElevenInstaller() casa_installer = CasaInstaller() jans_link_installer = JansLinkInstaller() + jans_saml_installer = JansSamlInstaller() jansCliInstaller = JansCliInstaller() @@ -453,6 +456,15 @@ def do_installation(): not Config.installed_instance and Config.get(jansCliInstaller.install_var)): jansCliInstaller.start_installation() jansCliInstaller.configure() + + if (Config.installed_instance and jans_saml_installer.install_var in Config.addPostSetupService) or ( + not Config.installed_instance and Config.get(jans_saml_installer.install_var)): + jans_saml_installer.start_installation() + + + if Config.install_jans_cli: + jansCliInstaller.start_installation() + jansCliInstaller.configure() # if (Config.installed_instance and 'installOxd' in Config.addPostSetupService) or (not Config.installed_instance and Config.installOxd): # oxdInstaller.start_installation() diff --git a/jans-linux-setup/jans_setup/schema/jans_schema.json b/jans-linux-setup/jans_setup/schema/jans_schema.json index 6b953d2be50..3de74c8cc5e 100644 --- a/jans-linux-setup/jans_setup/schema/jans_schema.json +++ b/jans-linux-setup/jans_setup/schema/jans_schema.json @@ -776,7 +776,7 @@ "names": [ "jansPersistentJWT" ], - "multivalued": true, + "multivalued": true, "oid": "jansAttr", "substr": "caseIgnoreSubstringsMatch", "syntax": "1.3.6.1.4.1.1466.115.121.1.15", @@ -1679,16 +1679,16 @@ "x_origin": "Jans created attribute" }, { - "desc": "Script object DN", - "equality": "distinguishedNameMatch", + "desc": "Script object DN", + "equality": "distinguishedNameMatch", "names": [ "jansScrDn" - ], + ], "multivalued": true, - "oid": "jansAttr", - "syntax": "1.3.6.1.4.1.1466.115.121.1.12", + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.12", "x_origin": "Jans created attribute" - }, + }, { "desc": "Attr that contains script type (e.g. python, java script)", "equality": "caseIgnoreMatch", @@ -3078,70 +3078,70 @@ "x_origin": "Jans created attribute" }, { - "desc": "Primary Key Attribute Name", - "equality": "caseIgnoreMatch", + "desc": "Primary Key Attribute Name", + "equality": "caseIgnoreMatch", "names": [ "jansPrimaryKeyAttrName" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jans created attribute" - }, - { - "desc": "Primary Key Value", - "equality": "caseIgnoreMatch", - "names": [ - "jansPrimaryKeyValue" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jans created attribute" - }, - { - "desc": "Secondary Key Attribute Name", - "equality": "caseIgnoreMatch", - "names": [ - "jansSecondaryKeyAttrName" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jansuu created attribute" - }, - { - "desc": "Secondary Key Value", - "equality": "caseIgnoreMatch", - "names": [ - "jansSecondaryKeyValue" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jans created attribute" - }, - { - "desc": "Tertiary Key Attribute Name", - "equality": "caseIgnoreMatch", - "names": [ - "jansTertiaryKeyAttrName" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jans created attribute" - }, - { - "desc": "Tertiary Key Value", - "equality": "caseIgnoreMatch", - "names": [ - "jansTertiaryKeyValue" - ], - "oid": "jansAttr", - "substr": "caseIgnoreSubstringsMatch", - "syntax": "1.3.6.1.4.1.1466.115.121.1.15", - "x_origin": "Jans created attribute" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Primary Key Value", + "equality": "caseIgnoreMatch", + "names": [ + "jansPrimaryKeyValue" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Secondary Key Attribute Name", + "equality": "caseIgnoreMatch", + "names": [ + "jansSecondaryKeyAttrName" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jansuu created attribute" + }, + { + "desc": "Secondary Key Value", + "equality": "caseIgnoreMatch", + "names": [ + "jansSecondaryKeyValue" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Tertiary Key Attribute Name", + "equality": "caseIgnoreMatch", + "names": [ + "jansTertiaryKeyAttrName" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Tertiary Key Value", + "equality": "caseIgnoreMatch", + "names": [ + "jansTertiaryKeyValue" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" }, { "desc": "Details of a running agama flow instance", @@ -3242,7 +3242,245 @@ "substr": "caseIgnoreSubstringsMatch", "syntax": "1.3.6.1.4.1.1466.115.121.1.15", "x_origin": "Jans created attribute" - } + }, + { + "equality": "caseIgnoreMatch", + "names": [ + "jansValidationStatus" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Used to specify if a SAML Trust Relationship is a federation. It could also be a website", + "equality": "caseIgnoreMatch", + "names": [ + "jansIsFed" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "SAML Trust Relationship attribute", + "equality": "caseIgnoreMatch", + "names": [ + "jansProfileConf" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "dn of the released attribute", + "equality": "caseIgnoreMatch", + "names": [ + "jansReleasedAttr" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Metadata filter in SAML trust relationship", + "equality": "caseIgnoreMatch", + "names": [ + "jansSAMLMetaDataFilter" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "SAML Trust Relationship SP metadata type - file, URI, federation", + "equality": "caseIgnoreMatch", + "names": [ + "jansSAMLspMetaDataSourceTyp" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "SAML Trust Relationship URI location of metadata", + "equality": "caseIgnoreMatch", + "names": [ + "jansSAMLspMetaDataURL" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "equality": "caseIgnoreMatch", + "names": [ + "jansValidationLog" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Specifies SAML trust relationship entity ID", + "equality": "caseIgnoreMatch", + "names": [ + "jansEntityId" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "This data has information about TR EntityType", + "equality": "caseIgnoreMatch", + "names": [ + "jansEntityTyp" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "This data has information about client type", + "equality": "caseIgnoreMatch", + "names": [ + "clientType" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "equality": "booleanMatch", + "names": [ + "displayInConsole" + ], + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.7", + "x_origin": "Jans created attribute" + }, + { + "desc": "SP Root URL", + "equality": "caseIgnoreMatch", + "names": [ + "rootUrl" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Default URL to use when the auth server needs to redirect or link back to the client.", + "equality": "caseIgnoreMatch", + "names": [ + "baseUrl" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "URL to the admin interface of the client.", + "equality": "caseIgnoreMatch", + "names": [ + "adminUrl" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "Specifies if users have to consent to client access.", + "equality": "booleanMatch", + "names": [ + "surrogateAuthRequired" + ], + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.7", + "x_origin": "Jans created attribute" + }, + { + "desc": "Specifies if users have to consent to client access.", + "equality": "booleanMatch", + "names": [ + "consentRequired" + ], + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.7", + "x_origin": "Jans created attribute" + }, + { + "equality": "booleanMatch", + "names": [ + "publicClient" + ], + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.7", + "x_origin": "Jans created attribute" + }, + { + "equality": "booleanMatch", + "names": [ + "frontchannelLogout" + ], + "oid": "jansAttr", + "syntax": "1.3.6.1.4.1.1466.115.121.1.7", + "x_origin": "Jans created attribute" + }, + { + "desc": "Client protocol", + "equality": "caseIgnoreMatch", + "names": [ + "protocol" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "jans Web origins URI", + "equality": "caseIgnoreMatch", + "names": [ + "jansWebOrigins" + ], + "multivalued": true, + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + }, + { + "desc": "SAML Trust Relationship file location of metadata", + "equality": "caseIgnoreMatch", + "names": [ + "jansSAMLspMetaDataFN" + ], + "oid": "jansAttr", + "substr": "caseIgnoreSubstringsMatch", + "syntax": "1.3.6.1.4.1.1466.115.121.1.15", + "x_origin": "Jans created attribute" + } ], "objectClasses": [ { @@ -3359,7 +3597,14 @@ "top" ], "x_origin": "Jans created objectclass", - "sql": {"include": ["telephoneNumber"], "includeObjectClass": ["jansCustomPerson"]} + "sql": { + "include": [ + "telephoneNumber" + ], + "includeObjectClass": [ + "jansCustomPerson" + ] + } }, { "kind": "STRUCTURAL", @@ -3581,7 +3826,9 @@ "top" ], "x_origin": "Jans created objectclass", - "sql": {"ignore": true} + "sql": { + "ignore": true + } }, { "kind": "STRUCTURAL", @@ -4316,17 +4563,17 @@ "jansSecondaryKeyValue", "jansTertiaryKeyAttrName", "jansTertiaryKeyValue" - ], + ], "must": [ "objectclass" - ], + ], "names": [ "jansInumMap" - ], + ], "oid": "jansObjClass", "sup": [ "top" - ], + ], "x_origin": "Jans created objectclass" }, { @@ -4397,8 +4644,8 @@ "x_origin": "Jans created objectclass" }, { - "kind": "STRUCTURAL", - "may": [ + "kind": "STRUCTURAL", + "may": [ "inum", "ou", "displayName", @@ -4409,7 +4656,7 @@ "jansLevel", "jansRevision", "jansEnabled", - "jansAlias" + "jansAlias" ], "must": [ "objectclass" @@ -4422,6 +4669,55 @@ "top" ], "x_origin": "Jans created objectclass" + }, + { + "kind": "STRUCTURAL", + "may": [ + "inum", + "owner", + "jansClntId", + "displayName", + "description", + "rootUrl", + "baseUrl", + "adminUrl", + "surrogateAuthRequired", + "jansEnabled", + "displayInConsole", + "jansPreferredMethod", + "jansClntSecret", + "jansRegistrationAccessTkn", + "jansRedirectURI", + "jansWebOrigins", + "consentRequired", + "jansSAMLMetaDataFilter", + "jansSAMLspMetaDataSourceTyp", + "jansSAMLspMetaDataFN", + "jansSAMLspMetaDataURL", + "jansMetaLocation", + "jansIsFed", + "jansEntityId", + "jansEntityTyp", + "jansProfileConf", + "jansReleasedAttr", + "url", + "jansPostLogoutRedirectURI", + "protocol", + "jansStatus", + "jansValidationStatus", + "jansValidationLog" + ], + "must": [ + "objectclass" + ], + "names": [ + "jansSAMLconfig" + ], + "oid": "jansObjClass", + "sup": [ + "top" + ], + "x_origin": "Jans created objectclass" } ], "oidMacros": { @@ -4433,4 +4729,4 @@ "jansReserved": "jansOrgOID:0", "jansSyntax": "jansPublished:1" } -} +} \ No newline at end of file diff --git a/jans-linux-setup/jans_setup/setup_app/config.py b/jans-linux-setup/jans_setup/setup_app/config.py index 1d72a4913ab..bd67ec77977 100644 --- a/jans-linux-setup/jans_setup/setup_app/config.py +++ b/jans-linux-setup/jans_setup/setup_app/config.py @@ -207,6 +207,7 @@ def progress(self, service_name, msg, incr=False): self.install_jans_link = True self.loadTestData = False self.allowPreReleasedFeatures = False + self.install_jans_saml = False # backward compatibility self.os_type = base.os_type diff --git a/jans-linux-setup/jans_setup/setup_app/installers/config_api.py b/jans-linux-setup/jans_setup/setup_app/installers/config_api.py index 140801c74e6..90031e64231 100644 --- a/jans-linux-setup/jans_setup/setup_app/installers/config_api.py +++ b/jans-linux-setup/jans_setup/setup_app/installers/config_api.py @@ -25,6 +25,7 @@ class ConfigApiInstaller(JettyInstaller): (os.path.join(Config.dist_jans_dir, 'user-mgt-plugin.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-config-api/plugins/user-mgt-plugin/{0}/user-mgt-plugin-{0}-distribution.jar').format(base.current_app.app_info['jans_version'])), (os.path.join(Config.dist_jans_dir, 'fido2-plugin.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-config-api/plugins/fido2-plugin/{0}/fido2-plugin-{0}-distribution.jar').format(base.current_app.app_info['jans_version'])), (os.path.join(Config.dist_jans_dir, 'jans-link-plugin.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-config-api/plugins/jans-link-plugin/{0}/jans-link-plugin-{0}-distribution.jar').format(base.current_app.app_info['jans_version'])), + (os.path.join(Config.dist_jans_dir, 'saml-plugin.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-config-api/plugins/saml-plugin/{0}/saml-plugin-{0}-distribution.jar').format(base.current_app.app_info['jans_version'])), ] def __init__(self): @@ -70,6 +71,9 @@ def install(self): if Config.install_jans_link: self.install_plugin('jans-link-plugin') + if Config.install_jans_saml: + self.install_plugin('saml-plugin') + self.enable() diff --git a/jans-linux-setup/jans_setup/setup_app/installers/jans.py b/jans-linux-setup/jans_setup/setup_app/installers/jans.py index dd481ab4f88..982210d7d91 100644 --- a/jans-linux-setup/jans_setup/setup_app/installers/jans.py +++ b/jans-linux-setup/jans_setup/setup_app/installers/jans.py @@ -79,6 +79,8 @@ def get_install_string(prefix, install_var): txt += get_install_string('Install Scim Server', 'install_scim_server') txt += get_install_string('Install Jans Link Server', 'install_jans_link') txt += get_install_string('Install Jans Casa Server', 'install_casa') + txt += get_install_string('Install Jans SAML', 'install_jans_saml') + if Config.profile == 'jans' and Config.installEleven: txt += get_install_string('Install Eleven Server', 'installEleven') diff --git a/jans-linux-setup/jans_setup/setup_app/installers/jans_saml.py b/jans-linux-setup/jans_setup/setup_app/installers/jans_saml.py new file mode 100644 index 00000000000..5ece5dfdcf0 --- /dev/null +++ b/jans-linux-setup/jans_setup/setup_app/installers/jans_saml.py @@ -0,0 +1,133 @@ +import os +import glob +import shutil + +from setup_app import paths +from setup_app.utils import base +from setup_app.utils.package_utils import packageUtils +from setup_app.static import AppType, InstallOption +from setup_app.config import Config +from setup_app.installers.jetty import JettyInstaller +from setup_app.utils.ldif_utils import create_client_ldif + + +class JansSamlInstaller(JettyInstaller): + + source_files = [ + (os.path.join(Config.dist_jans_dir, 'jans-keycloak-storage-api.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-keycloak-storage-api/{0}/jans-keycloak-storage-api-{0}.jar').format(base.current_app.app_info['jans_version'])), + (os.path.join(Config.dist_jans_dir, 'jans-scim-model.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-scim-model/{0}/jans-scim-model-{0}.jar').format(base.current_app.app_info['jans_version'])), + (os.path.join(Config.dist_jans_dir, 'jans-keycloak-storage-api.zip'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/jans-keycloak-storage-api/{0}/jans-keycloak-storage-api-{0}.zip').format(base.current_app.app_info['jans_version'])), + (os.path.join(Config.dist_app_dir, 'keycloak.zip'), 'https://github.com/keycloak/keycloak/releases/download/{0}/keycloak-{0}.zip'.format(base.current_app.app_info['KC_VERSION'])), + (os.path.join(Config.dist_jans_dir, 'kc-jans-authn-plugin.jar'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/kc-jans-authn-plugin/{0}/kc-jans-authn-plugin-{0}.jar').format(base.current_app.app_info['jans_version'])), + (os.path.join(Config.dist_jans_dir, 'kc-jans-authn-plugin-deps.zip'), os.path.join(base.current_app.app_info['JANS_MAVEN'], 'maven/io/jans/kc-jans-authn-plugin/{0}/kc-jans-authn-plugin-{0}-deps.zip').format(base.current_app.app_info['jans_version'])), + ] + + def __init__(self): + setattr(base.current_app, self.__class__.__name__, self) + self.service_name = 'jans-saml' + self.needdb = True + self.app_type = AppType.SERVICE + self.install_type = InstallOption.OPTONAL + self.install_var = 'install_jans_saml' + self.register_progess() + + self.saml_enabled = True + self.config_generation = True + self.ignore_validation = True + self.idp_root_dir = os.path.join(Config.opt_dir, 'idp/configs/') + + # sample config + self.idp_config_id = 'keycloak' + self.idp_config_root_dir = os.path.join(self.idp_root_dir, self.idp_config_id) + self.idp_config_enabled = 'true' + self.idp_config_temp_meta_dir = os.path.join(self.idp_root_dir, self.idp_config_id, 'temp_metadata') + self.idp_config_meta_dir = os.path.join(self.idp_root_dir, self.idp_config_id, 'metadata') + + self.idp_config_data_dir = os.path.join(Config.opt_dir, self.idp_config_id) + self.idp_config_log_dir = os.path.join(self.idp_config_data_dir, 'logs') + self.idp_config_providers_dir = os.path.join(self.idp_config_data_dir, 'providers') + + self.output_folder = os.path.join(Config.output_dir, self.service_name) + self.templates_folder = os.path.join(Config.templateFolder, self.service_name) + self.ldif_config_fn = os.path.join(self.output_folder, 'configuration.ldif') + self.config_json_fn = os.path.join(self.templates_folder, 'jans-saml-config.json') + self.idp_config_providers_fn = os.path.join(self.templates_folder, 'jans-keycloak-storage-api.properties') + self.clients_ldif_fn = os.path.join(self.output_folder, 'clients.ldif') + + # change this when we figure out this + Config.keycloack_hostname = 'localhost' + + if not base.argsp.shell: + self.extract_files() + + def install(self): + """installation steps""" + self.create_scim_client() + self.copy_files() + self.install_keycloack() + + def extract_files(self): + base.extract_file(base.current_app.jans_zip, 'jans-keycloak/storage-api/src/main/resources/jans-keycloak-storage-api.properties', self.templates_folder) + + + def render_import_templates(self): + self.logIt("Preparing base64 encodings configuration files") + self.renderTemplateInOut(self.config_json_fn, self.templates_folder, self.output_folder, pystring=True) + Config.templateRenderingDict['saml_dynamic_conf_base64'] = self.generate_base64_ldap_file( + os.path.join( + self.output_folder, + os.path.basename(self.config_json_fn) + ) + ) + + self.renderTemplateInOut(self.ldif_config_fn, self.templates_folder, self.output_folder) + self.dbUtils.import_ldif([self.ldif_config_fn]) + + + def create_folders(self): + for saml_dir in (self.idp_root_dir, self.idp_config_root_dir, self.idp_config_temp_meta_dir, self.idp_config_meta_dir, + self.idp_config_data_dir, self.idp_config_log_dir, self.idp_config_providers_dir, + ): + self.createDirs(saml_dir) + + self.chown(self.idp_root_dir, Config.jetty_user, Config.jetty_group, recursive=True) + + def create_scim_client(self): + result = self.check_clients([('saml_scim_client_id', '2100.')]) + if result.get('2100.') == -1: + + scopes = ['inum=F0C4,ou=scopes,o=jans'] + users_write_search_result = self.dbUtils.search('ou=scopes,o=jans', search_filter='(jansId=https://jans.io/scim/users.write)') + if users_write_search_result: + scopes.append(users_write_search_result['dn']) + users_read_search_result = self.dbUtils.search('ou=scopes,o=jans', search_filter='(jansId=https://jans.io/scim/users.read)') + if users_read_search_result: + scopes.append(users_read_search_result['dn']) + + create_client_ldif( + ldif_fn=self.clients_ldif_fn, + client_id=Config.saml_scim_client_id, + encoded_pw=Config.saml_scim_client_encoded_pw, + scopes=scopes, + redirect_uri=['https://{}/admin-ui'.format(Config.hostname), 'http://localhost:4100'], + display_name="Jans SCIM Client for SAML", + grant_types=['authorization_code', 'client_credentials', 'password', 'refresh_token'], + authorization_methods=['client_secret_basic', 'client_secret_post'] + ) + + self.dbUtils.import_ldif([self.clients_ldif_fn]) + + self.renderTemplateInOut(self.idp_config_providers_fn, self.templates_folder, self.idp_config_providers_dir, pystring=True) + + def copy_files(self): + self.copyFile(self.source_files[0][0], self.idp_config_providers_dir) + self.copyFile(self.source_files[1][0], self.idp_config_providers_dir) + base.unpack_zip(self.source_files[2][0], self.idp_config_providers_dir) + + + def install_keycloack(self): + self.logIt("Installing KC", pbar=self.service_name) + base.unpack_zip(self.source_files[3][0], self.idp_config_data_dir, with_par_dir=False) + self.copyFile(self.source_files[4][0], self.idp_config_providers_dir) + base.unpack_zip(self.source_files[5][0], self.idp_config_providers_dir) + diff --git a/jans-linux-setup/jans_setup/setup_app/setup_options.py b/jans-linux-setup/jans_setup/setup_app/setup_options.py index 9dc303fda2c..fd00c09d1c0 100644 --- a/jans-linux-setup/jans_setup/setup_app/setup_options.py +++ b/jans-linux-setup/jans_setup/setup_app/setup_options.py @@ -20,6 +20,7 @@ def get_setup_options(): 'installEleven': False, 'install_jans_link': True, 'install_casa': False, + 'install_jans_saml': False, 'loadTestData': False, 'allowPreReleasedFeatures': False, 'listenAllInterfaces': False, @@ -108,6 +109,8 @@ def get_setup_options(): if base.argsp.with_casa: setupOptions['install_casa'] = True + if base.argsp.install_jans_saml: + setupOptions['install_jans_saml'] = True if base.argsp.jans_max_mem: setupOptions['jans_max_mem'] = base.argsp.jans_max_mem diff --git a/jans-linux-setup/jans_setup/setup_app/utils/arg_parser.py b/jans-linux-setup/jans_setup/setup_app/utils/arg_parser.py index 4e176961a66..7a842952158 100644 --- a/jans-linux-setup/jans_setup/setup_app/utils/arg_parser.py +++ b/jans-linux-setup/jans_setup/setup_app/utils/arg_parser.py @@ -98,7 +98,7 @@ parser.add_argument('--install-eleven', help="Install Eleven Server", action='store_true') parser.add_argument('--install-jans-link', help="Install Link Server", action='store_true') parser.add_argument('--with-casa', help="Install Gluu Casa Server", action='store_true') - + parser.add_argument('--install-jans-saml', help="Install Jans SAML", action='store_true') #parser.add_argument('--oxd-use-jans-storage', help="Use Jans Storage for Oxd Server", action='store_true') parser.add_argument('--load-config-api-test', help="Load Config Api Test Data", action='store_true') diff --git a/jans-linux-setup/jans_setup/setup_app/utils/base.py b/jans-linux-setup/jans_setup/setup_app/utils/base.py index e8e9a3108ca..8fdc754fdfb 100644 --- a/jans-linux-setup/jans_setup/setup_app/utils/base.py +++ b/jans-linux-setup/jans_setup/setup_app/utils/base.py @@ -421,11 +421,25 @@ def extract_subdir(zip_fn, sub_dir, target_dir, par_dir=None): shutil.unpack_archive(zip_fn, unpack_dir, format='zip') shutil.copytree(os.path.join(unpack_dir, par_dir, sub_dir), target_dir) -def unpack_zip(zip_fn, extract_dir): - with zipfile.ZipFile(zip_fn, 'r') as zf: - for info in zf.infolist(): - zf.extract(info.filename, path=extract_dir) - out_path = os.path.join(extract_dir, info.filename) +def unpack_zip(zip_fn, extract_dir, with_par_dir=True): + logIt(f"Extracting {zip_fn} to {extract_dir} with parent directory {with_par_dir}") + if not with_par_dir and not os.path.exists(extract_dir): + os.makedirs(extract_dir) + + with zipfile.ZipFile(zip_fn) as zip_file: + info_list = zip_file.infolist() + for info in info_list: + fpath = Path(info.filename) + n = 0 if with_par_dir else 1 + out_path = os.path.join(extract_dir, *fpath.parts[n:]) + + if info.is_dir(): + if not os.path.exists(out_path): + os.makedirs(out_path) + else: + with open(out_path, 'wb') as w: + w.write(zip_file.read(info)) + perm = info.external_attr >> 16 os.chmod(out_path, perm) diff --git a/jans-linux-setup/jans_setup/setup_app/utils/collect_properties.py b/jans-linux-setup/jans_setup/setup_app/utils/collect_properties.py index 60b3a78b70c..c72a0bf648c 100644 --- a/jans-linux-setup/jans_setup/setup_app/utils/collect_properties.py +++ b/jans-linux-setup/jans_setup/setup_app/utils/collect_properties.py @@ -155,6 +155,7 @@ def collect(self): ('scim_client_id', '1201.', {'pw': 'scim_client_pw', 'encoded':'scim_client_encoded_pw'}), ('admin_ui_client_id', '1901.', {'pw': 'admin_ui_client_pw', 'encoded': 'admin_ui_client_encoded_pw'}), ('casa_client_id', CasaInstaller.client_id_prefix), + ('saml_scim_client_id', '2100.'), ] self.check_clients(client_var_id_list, create=False) diff --git a/jans-linux-setup/jans_setup/setup_app/utils/ldif_utils.py b/jans-linux-setup/jans_setup/setup_app/utils/ldif_utils.py index 0a9d6ae6d76..22799855331 100644 --- a/jans-linux-setup/jans_setup/setup_app/utils/ldif_utils.py +++ b/jans-linux-setup/jans_setup/setup_app/utils/ldif_utils.py @@ -156,10 +156,19 @@ def schema2json(schema_file, out_dir=None): with open(out_file, 'w') as w: w.write(schema_str) -def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, display_name, trusted_client='false'): +def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, display_name, trusted_client='false', grant_types=None, authorization_methods=None): + # create directory if not exists + dirname = os.path.dirname(ldif_fn) + if not os.path.exists(dirname): + os.makedirs(dirname) + clients_ldif_fd = open(ldif_fn, 'wb') ldif_clients_writer = LDIFWriter(clients_ldif_fd, cols=1000) client_dn = 'inum={},ou=clients,o=jans'.format(client_id) + if not grant_types: + grant_types = ['authorization_code', 'refresh_token', 'client_credentials'] + if not authorization_methods: + authorization_methods = ['client_secret_basic'] ldif_clients_writer.unparse( client_dn, { @@ -173,7 +182,7 @@ def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, dis 'jansAttrs': ['{"tlsClientAuthSubjectDn":"","runIntrospectionScriptBeforeJwtCreation":false,"keepClientAuthorizationAfterExpiration":false,"allowSpontaneousScopes":false,"spontaneousScopes":[],"spontaneousScopeScriptDns":[],"backchannelLogoutUri":[],"backchannelLogoutSessionRequired":false,"additionalAudience":[],"postAuthnScripts":[],"consentGatheringScripts":[],"introspectionScripts":[],"rptClaimsScripts":[]}'], 'jansClntSecret': [encoded_pw], 'jansDisabled': ['false'], - 'jansGrantTyp': ['authorization_code', 'refresh_token', 'client_credentials'], + 'jansGrantTyp': grant_types, 'jansIdTknSignedRespAlg': ['RS256'], 'jansInclClaimsInIdTkn': ['false'], 'jansLogoutSessRequired': ['false'], @@ -182,7 +191,7 @@ def create_client_ldif(ldif_fn, client_id, encoded_pw, scopes, redirect_uri, dis 'jansRptAsJwt': ['false'], 'jansScope': scopes, 'jansSubjectTyp': ['pairwise'], - 'jansTknEndpointAuthMethod': ['client_secret_basic'], + 'jansTknEndpointAuthMethod': authorization_methods, 'jansTrustedClnt': [trusted_client], 'jansRedirectURI': redirect_uri }) diff --git a/jans-linux-setup/jans_setup/setup_app/utils/properties_utils.py b/jans-linux-setup/jans_setup/setup_app/utils/properties_utils.py index db2df8925cc..d8ae506feac 100644 --- a/jans-linux-setup/jans_setup/setup_app/utils/properties_utils.py +++ b/jans-linux-setup/jans_setup/setup_app/utils/properties_utils.py @@ -625,6 +625,27 @@ def prompt_for_casa(self): Config.addPostSetupService.append('install_casa') + + def prompt_for_jans_saml(self): + if Config.installed_instance and Config.install_jans_saml: + return + + prompt = self.getPrompt("Install Jans SAML?", + self.getDefaultOption(Config.install_jans_saml) + )[0].lower() + + Config.install_jans_saml = prompt == 'y' + if Config.install_jans_saml: + while True: + selected_idp = self.getPrompt(" Please enter selected IDP") + if selected_idp: + Config.saml_selected_idp = selected_idp + break + + if Config.installed_instance and Config.install_jans_saml: + Config.addPostSetupService.append('install_jans_saml') + + def promptForConfigApi(self): if Config.installed_instance and Config.install_config_api: return @@ -975,6 +996,7 @@ def promptForProperties(self): self.prompt_for_jans_link() self.prompt_for_casa() + self.prompt_for_jans_saml() #self.promptForEleven() #if (not Config.installOxd) and Config.oxd_package: # self.promptForOxd() diff --git a/jans-linux-setup/jans_setup/templates/jans-config-api/dynamic-conf.json b/jans-linux-setup/jans_setup/templates/jans-config-api/dynamic-conf.json index c73231883d3..2f0f17fb8c7 100644 --- a/jans-linux-setup/jans_setup/templates/jans-config-api/dynamic-conf.json +++ b/jans-linux-setup/jans_setup/templates/jans-config-api/dynamic-conf.json @@ -92,7 +92,13 @@ "name": "jans-link", "description": "jans-link plugin", "className": "io.jans.configapi.plugin.link.rest.ApiApplication" + }, + { + "name": "saml", + "description": "saml plugin", + "className": "io.jans.configapi.plugin.saml.rest.ApiApplication" } + ] } diff --git a/jans-linux-setup/jans_setup/templates/jans-saml/configuration.ldif b/jans-linux-setup/jans_setup/templates/jans-saml/configuration.ldif new file mode 100644 index 00000000000..9bb99c31b03 --- /dev/null +++ b/jans-linux-setup/jans_setup/templates/jans-saml/configuration.ldif @@ -0,0 +1,6 @@ +dn: ou=jans-saml,ou=configuration,o=jans +jansConfDyn::%(saml_dynamic_conf_base64)s +jansRevision: 1 +objectClass: top +objectClass: jansAppConf +ou: jans-saml diff --git a/jans-linux-setup/jans_setup/templates/jans-saml/jans-saml-config.json b/jans-linux-setup/jans_setup/templates/jans-saml/jans-saml-config.json new file mode 100644 index 00000000000..00be42f3ffb --- /dev/null +++ b/jans-linux-setup/jans_setup/templates/jans-saml/jans-saml-config.json @@ -0,0 +1,22 @@ +{ + "applicationName":"saml", + "samlTrustRelationshipDn":"ou=trustRelationships,o=jans", + "samlEnabled": "${saml_enabled}", + "selectedIdp": "${saml_selected_idp}", + "idpRootDir": "${idp_root_dir}", + "idpMetadataFilePattern":"%s-idp-metadata.xml", + "spMetadataFilePattern":"%s-sp-metadata.xml", + "spMetadataFile":"sp-metadata.xml", + "configGeneration": "${config_generation}", + "ignoreValidation": "${ignore_validation}", + "idpConfigs":[ + { + "configId":"${idp_config_id}", + "rootDir":"${idp_config_root_dir}", + "enabled": "${idp_config_enabled}", + "metadataTempDir": "${idp_config_temp_meta_dir}", + "metadataDir":"${idp_config_meta_dir}", + "metadataFilePattern":"%s-idp-metadata.xml" + } + ] +} diff --git a/jans-linux-setup/jans_setup/templates/jans.properties b/jans-linux-setup/jans_setup/templates/jans.properties index a2a08d90bb1..55eebff8e02 100644 --- a/jans-linux-setup/jans_setup/templates/jans.properties +++ b/jans-linux-setup/jans_setup/templates/jans.properties @@ -5,6 +5,7 @@ fido2_ConfigurationEntryDN=ou=jans-fido2,ou=configuration,o=jans scim_ConfigurationEntryDN=ou=jans-scim,ou=configuration,o=jans configApi_ConfigurationEntryDN=ou=jans-config-api,ou=configuration,o=jans link_ConfigurationEntryDN=ou=jans-link,ou=configuration,o=jans +saml_ConfigurationEntryDN=ou=jans-saml,ou=configuration,o=jans certsDir=%(certFolder)s confDir=