From 90fde59c7810dbd7e0076292e95d971e2a5402e8 Mon Sep 17 00:00:00 2001 From: Reingold Shekhtel <13565058+raikbitters@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:08:17 +0200 Subject: [PATCH] Hotfix release v5.11.2 (#312) * Move web models from commons model to auth service * Add ROLES_ATTRIBUTE to sync SAML roles * Add flow for providing Administrator role * Add docker-compose.yml for local testing * Add JavaDoc params * Add Lombok support * Add no-build-cache for Java build --- Dockerfile | 4 +- build.gradle | 4 + docker-compose.yml | 460 ++++++++++++++++++ .../endpoint/AuthConfigurationEndpoint.java | 5 + .../endpoint/OAuthConfigurationEndpoint.java | 1 + .../auth/integration/AuthIntegrationType.java | 2 +- .../integration/converter/SamlConverter.java | 5 +- .../impl/GetSamlIntegrationsStrategy.java | 2 +- .../integration/parameter/ParameterUtils.java | 1 + .../integration/parameter/SamlParameter.java | 3 +- .../integration/saml/SamlUserReplicator.java | 14 +- .../auth/model/SamlProvidersResource.java | 33 ++ .../reportportal/auth/model/SamlResource.java | 91 ++++ tests/requests/saml.http | 47 ++ 14 files changed, 663 insertions(+), 9 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/main/java/com/epam/reportportal/auth/model/SamlProvidersResource.java create mode 100644 src/main/java/com/epam/reportportal/auth/model/SamlResource.java create mode 100644 tests/requests/saml.http diff --git a/Dockerfile b/Dockerfile index 92c34f07..6c79cec7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,10 +4,10 @@ ARG APP_VERSION WORKDIR /usr/app COPY . /usr/app RUN if [ "${RELEASE_MODE}" = true ]; then \ - gradle build --exclude-task test \ + gradle build --no-build-cache --exclude-task test \ -PreleaseMode=true \ -Dorg.gradle.project.version=${APP_VERSION}; \ - else gradle build --exclude-task test -Dorg.gradle.project.version=${APP_VERSION}; fi + else gradle build --no-build-cache --exclude-task test -Dorg.gradle.project.version=${APP_VERSION}; fi # For ARM build use flag: `--platform linux/arm64` FROM --platform=$BUILDPLATFORM amazoncorretto:11.0.20 diff --git a/build.gradle b/build.gradle index c7a74394..5d21d78d 100644 --- a/build.gradle +++ b/build.gradle @@ -109,6 +109,10 @@ dependencies { implementation 'org.hibernate:hibernate-core:5.4.24.Final' implementation 'org.springframework:spring-core:5.3.30' implementation "com.rabbitmq:http-client:5.2.0" + + // Lombok + compileOnly 'org.projectlombok:lombok:1.18.32' + annotationProcessor 'org.projectlombok:lombok:1.18.32' } processResources { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9fc028aa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,460 @@ +version: "3.8" +services: + + ## External dependencies + + gateway: + image: traefik:v2.0.7 + container_name: traefik + logging: &logging + driver: "json-file" + options: + max-size: 100m + max-file: "5" + ports: + - "8080:8080" # ReportPortal UI + - "8081:8081" # Traefik dashboard + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: + - --providers.docker=true + - --providers.docker.constraints=Label(`traefik.expose`, `true`) + - --entrypoints.web.address=:8080 + - --entrypoints.traefik.address=:8081 + - --api.dashboard=true + - --api.insecure=true + networks: + - reportportal + restart: always + + opensearch: + image: opensearchproject/opensearch:2.11.0 + container_name: opensearch + logging: + <<: *logging + environment: + discovery.type: single-node + plugins.security.disabled: "true" + bootstrap.memory_lock: "true" + OPENSEARCH_JAVA_OPTS: -Xms512m -Xmx512m + DISABLE_INSTALL_DEMO_CONFIG: "true" + ulimits: + memlock: + soft: -1 + hard: -1 + ## Expose OpenSearch + # ports: + # - "9200:9200" + # - "9600:9600" + volumes: + - opensearch:/usr/share/opensearch/data + healthcheck: + test: ["CMD", "curl","-s" ,"-f", "http://0.0.0.0:9200/_cat/health"] + networks: + - reportportal + + postgres: + image: postgres:12.17-alpine3.17 + container_name: postgres + logging: + <<: *logging + shm_size: '512m' + environment: + POSTGRES_USER: &db_user rpuser + POSTGRES_PASSWORD: &db_password rppass + POSTGRES_DB: &db_name reportportal + volumes: + - postgres:/var/lib/postgresql/data + ## Expose Database + ports: + - "5432:5432" + command: + ## PostgrsSQL performance tuning + ## Ref: https://reportportal.io/docs/installation-steps/OptimalPerformanceHardwareSetup#5-postgresql-performance-tuning + -c checkpoint_completion_target=0.9 + -c work_mem=96MB + -c wal_writer_delay=20ms + -c synchronous_commit=off + -c wal_buffers=32MB + -c min_wal_size=2GB + -c max_wal_size=4GB + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER"] + interval: 10s + timeout: 120s + retries: 10 + networks: + - reportportal + restart: always + + rabbitmq: + image: bitnami/rabbitmq:3.12.2-debian-11-r8 + container_name: rabbitmq + logging: + <<: *logging + ## Expose RabbitMQ + # ports: + # - "5672:5672" + # - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: &rabbitmq_user rabbitmq + RABBITMQ_DEFAULT_PASS: &rabbitmq_password rabbitmq + healthcheck: + test: ["CMD", "rabbitmqctl", "status"] + interval: 30s + timeout: 30s + retries: 5 + networks: + - reportportal + restart: always + + ## ReportPortal services + + index: + image: reportportal/service-index:5.11.0 + container_name: reportportal-index + logging: + <<: *logging + depends_on: + gateway: + condition: service_started + environment: + LB_URL: http://gateway:8081 + TRAEFIK_V2_MODE: 'true' + healthcheck: + test: wget -q --spider http://0.0.0.0:8080/health + interval: 30s + timeout: 30s + retries: 10 + start_period: 10s + labels: + - "traefik.http.routers.index.rule=PathPrefix(`/`)" + - "traefik.http.routers.index.service=index" + - "traefik.http.services.index.loadbalancer.server.port=8080" + - "traefik.http.services.index.loadbalancer.server.scheme=http" + - "traefik.expose=true" + networks: + - reportportal + restart: always + + ui: + image: reportportal/service-ui:5.11.1 + container_name: reportportal-ui + environment: + RP_SERVER_PORT: "8080" + healthcheck: + test: wget -q --spider http://0.0.0.0:8080/health + interval: 30s + timeout: 30s + retries: 10 + start_period: 10s + labels: + - "traefik.http.middlewares.ui-strip-prefix.stripprefix.prefixes=/ui" + - "traefik.http.routers.ui.middlewares=ui-strip-prefix@docker" + - "traefik.http.routers.ui.rule=PathPrefix(`/ui`)" + - "traefik.http.routers.ui.service=ui" + - "traefik.http.services.ui.loadbalancer.server.port=8080" + - "traefik.http.services.ui.loadbalancer.server.scheme=http" + - "traefik.expose=true" + networks: + - reportportal + restart: always + + api: + image: reportportal/service-api:5.11.1 + container_name: reportportal-api + logging: + <<: *logging + depends_on: + rabbitmq: + condition: service_healthy + gateway: + condition: service_started + postgres: + condition: service_healthy + environment: + ## Double entry moves test logs from PostgreSQL to Elastic-type engines + ## Ref: https://reportportal.io/blog/double-entry-in-5.7.2 + ## RP_ELASTICSEARCH_HOST: http://opensearch:9200 + RP_DB_HOST: postgres + RP_DB_USER: *db_user + RP_DB_PASS: *db_password + RP_DB_NAME: *db_name + RP_AMQP_HOST: &rabbitmq_host rabbitmq + RP_AMQP_PORT: &rabbitmq_port 5672 + RP_AMQP_USER: *rabbitmq_user + RP_AMQP_PASS: *rabbitmq_password + RP_AMQP_APIUSER: *rabbitmq_user + RP_AMQP_APIPASS: *rabbitmq_password + RP_AMQP_ANALYZER-VHOST: analyzer + DATASTORE_TYPE: filesystem + LOGGING_LEVEL_ORG_HIBERNATE_SQL: info + RP_REQUESTLOGGING: "false" + AUDIT_LOGGER: "OFF" + MANAGEMENT_HEALTH_ELASTICSEARCH_ENABLED: "false" + RP_ENVIRONMENT_VARIABLE_ALLOW_DELETE_ACCOUNT: "false" + JAVA_OPTS: > + -Xmx1g + -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath=/tmp + -Dcom.sun.management.jmxremote.rmi.port=12349 + -Dcom.sun.management.jmxremote + -Dcom.sun.management.jmxremote.local.only=false + -Dcom.sun.management.jmxremote.port=9010 + -Dcom.sun.management.jmxremote.authenticate=false + -Dcom.sun.management.jmxremote.ssl=false + -Djava.rmi.server.hostname=0.0.0.0 + RP_JOBS_BASEURL: http://jobs:8686 + COM_TA_REPORTPORTAL_JOB_INTERRUPT_BROKEN_LAUNCHES_CRON: PT1H + RP_ENVIRONMENT_VARIABLE_PATTERN-ANALYSIS_BATCH-SIZE: 100 + RP_ENVIRONMENT_VARIABLE_PATTERN-ANALYSIS_PREFETCH-COUNT: 1 + RP_ENVIRONMENT_VARIABLE_PATTERN-ANALYSIS_CONSUMERS-COUNT: 1 + volumes: + - storage:/data/storage + healthcheck: + test: curl -f http://0.0.0.0:8585/health + interval: 60s + timeout: 30s + retries: 10 + start_period: 60s + labels: + - "traefik.http.middlewares.api-strip-prefix.stripprefix.prefixes=/api" + - "traefik.http.routers.api.middlewares=api-strip-prefix@docker" + - "traefik.http.routers.api.rule=PathPrefix(`/api`)" + - "traefik.http.routers.api.service=api" + - "traefik.http.services.api.loadbalancer.server.port=8585" + - "traefik.http.services.api.loadbalancer.server.scheme=http" + - "traefik.expose=true" + networks: + - reportportal + restart: always + + uat: + build: + context: . + dockerfile: Dockerfile + logging: + <<: *logging + environment: + RP_DB_HOST: postgres + RP_DB_USER: *db_user + RP_DB_PASS: *db_password + RP_DB_NAME: *db_name + RP_AMQP_HOST: *rabbitmq_host + RP_AMQP_PORT: *rabbitmq_port + RP_AMQP_USER: *rabbitmq_user + RP_AMQP_PASS: *rabbitmq_password + RP_AMQP_APIUSER: *rabbitmq_user + RP_AMQP_APIPASS: *rabbitmq_password + DATASTORE_TYPE: filesystem + RP_SESSION_LIVE: 86400 # in seconds + RP_SAML_SESSION-LIVE: 4320 + ## RP_INITIAL_ADMIN_PASSWORD - the initial password of the superadmin user for the first launch. This value can't change the password on redeployments. + RP_INITIAL_ADMIN_PASSWORD: "erebus" + JAVA_OPTS: -Djava.security.egd=file:/dev/./urandom -XX:MinRAMPercentage=60.0 -XX:MaxRAMPercentage=90.0 + volumes: + - storage:/data/storage + healthcheck: + test: curl -f http://0.0.0.0:9999/health + interval: 60s + timeout: 30s + retries: 10 + start_period: 60s + labels: + - "traefik.http.middlewares.uat-strip-prefix.stripprefix.prefixes=/uat" + - "traefik.http.routers.uat.middlewares=uat-strip-prefix@docker" + - "traefik.http.routers.uat.rule=PathPrefix(`/uat`)" + - "traefik.http.routers.uat.service=uat" + - "traefik.http.services.uat.loadbalancer.server.port=9999" + - "traefik.http.services.uat.loadbalancer.server.scheme=http" + - "traefik.expose=true" + depends_on: + postgres: + condition: service_healthy + networks: + - reportportal + restart: always + + jobs: + image: reportportal/service-jobs:5.11.1 + container_name: reportportal-jobs + logging: + <<: *logging + depends_on: + rabbitmq: + condition: service_healthy + gateway: + condition: service_started + postgres: + condition: service_healthy + environment: + ## Double entry moves test logs from PostgreSQL to Elastic-type engines + #№ Ref: https://reportportal.io/blog/double-entry-in-5.7.2 + ## RP_ELASTICSEARCH_HOST: http://opensearch:9200 + ## RP_ELASTICSEARCH_USERNAME: "" + ## RP_ELASTICSEARCH_PASSWORD: "" + RP_DB_HOST: postgres + RP_DB_USER: *db_user + RP_DB_PASS: *db_password + RP_DB_NAME: *db_name + RP_AMQP_HOST: *rabbitmq_host + RP_AMQP_PORT: *rabbitmq_port + RP_AMQP_USER: *rabbitmq_user + RP_AMQP_PASS: *rabbitmq_password + RP_AMQP_APIUSER: *rabbitmq_user + RP_AMQP_APIPASS: *rabbitmq_password + RP_AMQP_ANALYZER-VHOST: analyzer + DATASTORE_TYPE: filesystem + RP_ENVIRONMENT_VARIABLE_CLEAN_ATTACHMENT_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_LOG_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_LAUNCH_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_STORAGE_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_STORAGE_PROJECT_CRON: 0 */5 * * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_EXPIREDUSER_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_EXPIREDUSER_RETENTIONPERIOD: 365 + RP_ENVIRONMENT_VARIABLE_NOTIFICATION_EXPIREDUSER_CRON: 0 0 */24 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_EVENTS_RETENTIONPERIOD: 365 + RP_ENVIRONMENT_VARIABLE_CLEAN_EVENTS_CRON: 0 30 05 * * * + RP_ENVIRONMENT_VARIABLE_CLEAN_STORAGE_CHUNKSIZE: 20000 + RP_PROCESSING_LOG_MAXBATCHSIZE: 2000 + RP_PROCESSING_LOG_MAXBATCHTIMEOUT: 6000 + RP_AMQP_MAXLOGCONSUMER: 1 + JAVA_OPTS: > + -Djava.security.egd=file:/dev/./urandom + -XX:+UseG1GC + -XX:+UseStringDeduplication + -XX:G1ReservePercent=20 + -XX:InitiatingHeapOccupancyPercent=60 + -XX:MaxRAMPercentage=70.0 + -XX:+HeapDumpOnOutOfMemoryError + -XX:HeapDumpPath=/tmp + volumes: + - storage:/data/storage + healthcheck: + test: curl -f http://0.0.0.0:8686/health || exit 1 + interval: 60s + timeout: 30s + retries: 10 + start_period: 60s + labels: + - traefik.http.middlewares.jobs-strip-prefix.stripprefix.prefixes=/jobs + - traefik.http.routers.jobs.middlewares=jobs-strip-prefix@docker + - traefik.http.routers.jobs.rule=PathPrefix(`/jobs`) + - traefik.http.routers.jobs.service=jobs + - traefik.http.services.jobs.loadbalancer.server.port=8686 + - traefik.http.services.jobs.loadbalancer.server.scheme=http + - traefik.expose=true + networks: + - reportportal + restart: always + + analyzer: + image: &analyzer_img reportportal/service-auto-analyzer:5.11.0-r1 + container_name: reportportal-analyzer + logging: + <<: *logging + environment: + LOGGING_LEVEL: info + AMQP_EXCHANGE_NAME: analyzer-default + AMQP_VIRTUAL_HOST: analyzer + AMQP_URL: amqp://rabbitmq:rabbitmq@rabbitmq:5672 + # ES_USER: + # ES_PASSWORD: + ES_HOSTS: http://opensearch:9200 + ANALYZER_BINARYSTORE_TYPE: filesystem + volumes: + - storage:/data/storage + depends_on: + opensearch: + condition: service_started + rabbitmq: + condition: service_healthy + networks: + - reportportal + restart: always + + analyzer-train: + image: *analyzer_img + container_name: reportportal-analyzer-train + logging: + <<: *logging + environment: + LOGGING_LEVEL: info + AMQP_EXCHANGE_NAME: analyzer-default + AMQP_VIRTUAL_HOST: analyzer + AMQP_URL: amqp://rabbitmq:rabbitmq@rabbitmq:5672 + # ES_USER: + # ES_PASSWORD: + ES_HOSTS: http://opensearch:9200 + INSTANCE_TASK_TYPE: train + UWSGI_WORKERS: 1 + ANALYZER_BINARYSTORE_TYPE: filesystem + volumes: + - storage:/data/storage + depends_on: + opensearch: + condition: service_started + rabbitmq: + condition: service_healthy + networks: + - reportportal + restart: always + + metrics-gatherer: + image: reportportal/service-metrics-gatherer:5.11.0-r1 + container_name: reportportal-metrics-gatherer + logging: + <<: *logging + environment: + LOGGING_LEVEL: info + # ES_USER: + # ES_PASSWORD: + ES_HOST: http://opensearch:9200 + POSTGRES_USER: *db_user + POSTGRES_PASSWORD: *db_password + POSTGRES_DB: *db_name + POSTGRES_HOST: postgres + POSTGRES_PORT: 5432 + ALLOWED_START_TIME: "22:00" + ALLOWED_END_TIME: "08:00" + # TZ: Europe/Minsk you can change a timezone like this to specify when metrics are gathered + AMQP_URL: amqp://rabbitmq:rabbitmq@rabbitmq:5672 + AMQP_VIRTUAL_HOST: analyzer + depends_on: + opensearch: + condition: service_started + networks: + - reportportal + restart: always + + migrations: + image: reportportal/migrations:5.11.0 + container_name: reportportal-migrations + logging: + <<: *logging + depends_on: + postgres: + condition: service_healthy + environment: + POSTGRES_SERVER: postgres + POSTGRES_PORT: 5432 + POSTGRES_DB: *db_name + POSTGRES_USER: *db_user + POSTGRES_PASSWORD: *db_password + OS_HOST: opensearch + OS_PORT: 9200 + OS_PROTOCOL: http + # OS_USER: + # OS_PASSWORD: + networks: + - reportportal + restart: on-failure + + +volumes: + opensearch: + storage: + postgres: + +networks: + reportportal: diff --git a/src/main/java/com/epam/reportportal/auth/endpoint/AuthConfigurationEndpoint.java b/src/main/java/com/epam/reportportal/auth/endpoint/AuthConfigurationEndpoint.java index ce4f7fff..00556378 100644 --- a/src/main/java/com/epam/reportportal/auth/endpoint/AuthConfigurationEndpoint.java +++ b/src/main/java/com/epam/reportportal/auth/endpoint/AuthConfigurationEndpoint.java @@ -58,6 +58,8 @@ public AuthConfigurationEndpoint(CreateAuthIntegrationHandler createAuthIntegrat * Creates or updates auth integration settings * * @param request Update request + * @param authType Type of Auth + * @param user User * @return Successful message or an error */ @Transactional @@ -73,6 +75,9 @@ public AbstractAuthResource createAuthIntegration(@RequestBody @Valid UpdateAuth * Creates or updates auth integration settings * * @param request Update request + * @param authType Type of Auth + * @param user User + * @param integrationId Integration ID * @return Successful message or an error */ @Transactional diff --git a/src/main/java/com/epam/reportportal/auth/endpoint/OAuthConfigurationEndpoint.java b/src/main/java/com/epam/reportportal/auth/endpoint/OAuthConfigurationEndpoint.java index 97eb2fbd..18d71016 100644 --- a/src/main/java/com/epam/reportportal/auth/endpoint/OAuthConfigurationEndpoint.java +++ b/src/main/java/com/epam/reportportal/auth/endpoint/OAuthConfigurationEndpoint.java @@ -59,6 +59,7 @@ public OAuthConfigurationEndpoint(CreateAuthIntegrationHandler createAuthIntegra * Updates oauth integration settings * * @param clientRegistrationResource OAuth configuration + * @param oauthProviderId Oauth settings Profile Id * @return All defined OAuth integration settings */ @Transactional diff --git a/src/main/java/com/epam/reportportal/auth/integration/AuthIntegrationType.java b/src/main/java/com/epam/reportportal/auth/integration/AuthIntegrationType.java index 33ce2c7a..07470a3f 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/AuthIntegrationType.java +++ b/src/main/java/com/epam/reportportal/auth/integration/AuthIntegrationType.java @@ -18,11 +18,11 @@ import com.epam.reportportal.auth.integration.converter.ActiveDirectoryConverter; import com.epam.reportportal.auth.integration.converter.LdapConverter; import com.epam.reportportal.auth.integration.converter.SamlConverter; +import com.epam.reportportal.auth.model.SamlResource; import com.epam.ta.reportportal.entity.integration.Integration; import com.epam.ta.reportportal.ws.model.integration.auth.AbstractAuthResource; import com.epam.ta.reportportal.ws.model.integration.auth.ActiveDirectoryResource; import com.epam.ta.reportportal.ws.model.integration.auth.LdapResource; -import com.epam.ta.reportportal.ws.model.integration.auth.SamlResource; import java.util.Arrays; import java.util.Optional; diff --git a/src/main/java/com/epam/reportportal/auth/integration/converter/SamlConverter.java b/src/main/java/com/epam/reportportal/auth/integration/converter/SamlConverter.java index d2262597..40624d9c 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/converter/SamlConverter.java +++ b/src/main/java/com/epam/reportportal/auth/integration/converter/SamlConverter.java @@ -16,10 +16,10 @@ package com.epam.reportportal.auth.integration.converter; import com.epam.reportportal.auth.integration.parameter.ParameterUtils; +import com.epam.reportportal.auth.model.SamlResource; +import com.epam.reportportal.auth.model.SamlProvidersResource; import com.epam.ta.reportportal.entity.integration.Integration; import com.epam.ta.reportportal.entity.integration.IntegrationType; -import com.epam.ta.reportportal.ws.model.integration.auth.SamlProvidersResource; -import com.epam.ta.reportportal.ws.model.integration.auth.SamlResource; import com.epam.ta.reportportal.ws.model.integration.auth.UpdateAuthRQ; import org.springframework.security.saml.provider.service.config.ExternalIdentityProviderConfiguration; import org.springframework.security.saml.saml2.metadata.BindingType; @@ -64,6 +64,7 @@ public class SamlConverter { IDP_METADATA_URL.getParameter(integration).ifPresent(resource::setIdentityProviderMetadataUrl); IDP_URL.getParameter(integration).ifPresent(resource::setIdentityProviderUrl); IDP_NAME_ID.getParameter(integration).ifPresent(resource::setIdentityProviderNameId); + ROLES_ATTRIBUTE.getParameter(integration).ifPresent(resource::setRolesAttribute); final IntegrationType integrationType = integration.getType(); ofNullable(integrationType.getDetails()).flatMap(typeDetails -> Optional.ofNullable(typeDetails.getDetails())) .flatMap(BASE_PATH::getParameter) diff --git a/src/main/java/com/epam/reportportal/auth/integration/handler/impl/GetSamlIntegrationsStrategy.java b/src/main/java/com/epam/reportportal/auth/integration/handler/impl/GetSamlIntegrationsStrategy.java index bb4f0839..34469d73 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/handler/impl/GetSamlIntegrationsStrategy.java +++ b/src/main/java/com/epam/reportportal/auth/integration/handler/impl/GetSamlIntegrationsStrategy.java @@ -2,6 +2,7 @@ import com.epam.reportportal.auth.integration.AuthIntegrationType; import com.epam.reportportal.auth.integration.handler.GetAuthIntegrationStrategy; +import com.epam.reportportal.auth.model.SamlProvidersResource; import com.epam.ta.reportportal.dao.IntegrationRepository; import com.epam.ta.reportportal.dao.IntegrationTypeRepository; import com.epam.ta.reportportal.entity.integration.Integration; @@ -9,7 +10,6 @@ import com.epam.ta.reportportal.exception.ReportPortalException; import com.epam.ta.reportportal.ws.model.ErrorType; import com.epam.ta.reportportal.ws.model.integration.auth.AbstractAuthResource; -import com.epam.ta.reportportal.ws.model.integration.auth.SamlProvidersResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/epam/reportportal/auth/integration/parameter/ParameterUtils.java b/src/main/java/com/epam/reportportal/auth/integration/parameter/ParameterUtils.java index 253aead6..41f058c9 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/parameter/ParameterUtils.java +++ b/src/main/java/com/epam/reportportal/auth/integration/parameter/ParameterUtils.java @@ -45,6 +45,7 @@ public static void setSamlParameters(UpdateAuthRQ request, Integration integrati IDP_NAME_ID.setParameter(request, integration); IDP_ALIAS.setParameter(request, integration); IDP_URL.setParameter(request, integration); + ROLES_ATTRIBUTE.setParameter(request, integration); FULL_NAME_ATTRIBUTE.getParameter(request).ifPresentOrElse(fullName -> { FIRST_NAME_ATTRIBUTE.removeParameter(integration); diff --git a/src/main/java/com/epam/reportportal/auth/integration/parameter/SamlParameter.java b/src/main/java/com/epam/reportportal/auth/integration/parameter/SamlParameter.java index 45cb35d5..2c490f5c 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/parameter/SamlParameter.java +++ b/src/main/java/com/epam/reportportal/auth/integration/parameter/SamlParameter.java @@ -45,7 +45,8 @@ public enum SamlParameter { IDP_URL("identityProviderUrl", false), FULL_NAME_ATTRIBUTE("fullNameAttribute", false), FIRST_NAME_ATTRIBUTE("firstNameAttribute", false), - LAST_NAME_ATTRIBUTE("lastNameAttribute", false); + LAST_NAME_ATTRIBUTE("lastNameAttribute", false), + ROLES_ATTRIBUTE("rolesAttribute", false); private String parameterName; diff --git a/src/main/java/com/epam/reportportal/auth/integration/saml/SamlUserReplicator.java b/src/main/java/com/epam/reportportal/auth/integration/saml/SamlUserReplicator.java index c02a07fa..d58287c7 100644 --- a/src/main/java/com/epam/reportportal/auth/integration/saml/SamlUserReplicator.java +++ b/src/main/java/com/epam/reportportal/auth/integration/saml/SamlUserReplicator.java @@ -112,7 +112,6 @@ public User replicateUser(ReportPortalSamlAuthentication samlAuthentication) { } user.setUserType(UserType.SAML); - user.setRole(UserRole.USER); user.setExpired(false); Project project = generatePersonalProject(user); @@ -160,6 +159,8 @@ private void populateUserDetails(User user, List details) { findAttributeValue(details, UserAttribute.FIRST_NAME.toString(), String.class); String lastName = findAttributeValue(details, UserAttribute.LAST_NAME.toString(), String.class); user.setFullName(String.join(" ", firstName, lastName)); + + user.setRole(UserRole.USER); } private void populateUserDetailsIfSettingsArePresent(User user, Integration integration, @@ -173,7 +174,7 @@ private void populateUserDetailsIfSettingsArePresent(User user, Integration inte Optional idpFullNameOptional = SamlParameter.FULL_NAME_ATTRIBUTE.getParameter(integration); - if (!idpFullNameOptional.isPresent()) { + if (idpFullNameOptional.isEmpty()) { String firstName = findAttributeValue(details, SamlParameter.FIRST_NAME_ATTRIBUTE.getParameter(integration).orElse(null), String.class ); @@ -185,6 +186,15 @@ private void populateUserDetailsIfSettingsArePresent(User user, Integration inte String fullName = findAttributeValue(details, idpFullNameOptional.get(), String.class); user.setFullName(fullName); } + + String roles = findAttributeValue(details, + SamlParameter.ROLES_ATTRIBUTE.getParameter(integration).orElse(null), String.class + ); + if (Objects.nonNull(roles) && roles.toLowerCase().contains("admin")) { + user.setRole(UserRole.ADMINISTRATOR); + } else { + user.setRole(UserRole.USER); + } } private T findAttributeValue(List attributes, String lookingFor, Class castTo) { diff --git a/src/main/java/com/epam/reportportal/auth/model/SamlProvidersResource.java b/src/main/java/com/epam/reportportal/auth/model/SamlProvidersResource.java new file mode 100644 index 00000000..3510bd3d --- /dev/null +++ b/src/main/java/com/epam/reportportal/auth/model/SamlProvidersResource.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.epam.reportportal.auth.model; + +import com.epam.ta.reportportal.ws.model.integration.auth.AbstractAuthResource; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.Valid; +import java.util.List; + +@Setter +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SamlProvidersResource extends AbstractAuthResource { + @Valid + private List providers; +} diff --git a/src/main/java/com/epam/reportportal/auth/model/SamlResource.java b/src/main/java/com/epam/reportportal/auth/model/SamlResource.java new file mode 100644 index 00000000..6e31b2c7 --- /dev/null +++ b/src/main/java/com/epam/reportportal/auth/model/SamlResource.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.epam.reportportal.auth.model; + +import com.epam.ta.reportportal.ws.model.integration.auth.AbstractAuthResource; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.annotations.ApiModel; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotEmpty; + +/** + * @author Ihar Kahadouski + */ + +@Setter +@Getter +@ApiModel +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SamlResource extends AbstractAuthResource { + + private Long id; + + @JsonProperty(value = "callbackUrl") + private String callbackUrl; + + /** + * Provider name associated with IDP + */ + @NotEmpty + private String identityProviderName; + /** + * Alias associated with IDP + */ + private String identityProviderAlias; + /** + * IDP metadata URL + */ + @NotEmpty + private String identityProviderMetadataUrl; + /** + * Attribute Name Format Id associated with IDP for user identification + */ + private String identityProviderNameId; + /** + * IDP URL + */ + private String identityProviderUrl; + /** + * Attribute name associated with full name of user in SAML response + */ + private String fullNameAttribute; + /** + * Attribute name associated with first name of user in SAML response + */ + private String firstNameAttribute; + /** + * Attribute name associated with last name of user in SAML response + */ + private String lastNameAttribute; + /** + * Attribute name associated with email of user in SAML response + */ + @NotEmpty + private String emailAttribute; + /** + * Attribute name associated with roles of user in SAML response + */ + private String rolesAttribute; + /** + * Indicates IDP availability for authentication + */ + private boolean enabled; + +} \ No newline at end of file diff --git a/tests/requests/saml.http b/tests/requests/saml.http new file mode 100644 index 00000000..2c9205ad --- /dev/null +++ b/tests/requests/saml.http @@ -0,0 +1,47 @@ +@token = "" + + +POST http://localhost:8080/uat/settings/auth/saml/ +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "enabled":true, + "integrationParameters": + { + "rolesAttribute":"jobRole", + "firstNameAttribute":"givenname", + "lastNameAttribute":"surname", + "emailAttribute":"mail", + "identityProviderMetadataUrl":"https://login.microsoftonline.com/...", + "identityProviderUrl":"https://sts.windows.net/...", + "identityProviderName":"Azure", + "identityProviderAlias":"https://sts.windows.net/...", + "identityProviderNameId":"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "callbackUrl":"http://localhost:8080/uat" + } +} + +### +@saml_resource="" + +PUT http://localhost:8080/uat/settings/auth/saml/{{saml_resource}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "enabled":true, + "integrationParameters": + { + "rolesAttribute":"jobRole", + "firstNameAttribute":"givenname", + "lastNameAttribute":"surname", + "emailAttribute":"mail", + "identityProviderMetadataUrl":"https://login.microsoftonline.com/...", + "identityProviderUrl":"https://sts.windows.net/...", + "identityProviderName":"Azure", + "identityProviderAlias":"https://sts.windows.net/...", + "identityProviderNameId":"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "callbackUrl":"http://localhost:8080/uat" + } +}